差分約束系統

參考博客:https://blog.csdn.net/dragon60066/article/details/80245797
此知識點需對最短路,最長路有了解。
差分約束系統應用一,
知道形如下面的不等式組
在這裏插入圖片描述
現在我們要求一組解x1=a1,x2=a2,這樣的解來滿足上面不等式組。
有一個性質,我們可以知道,如果有一組解x1=a1,x2=a2~~滿足上面不等式組,那麼這組解全部+d。即x1=a1+d,x2=a2+d,也是可滿足的。因爲做差之後d會被抵消掉。
現在,我們分析一個不等式
x1-x2<=k 轉化爲x1<=x2 +k在轉化x2>=x1-k;
此不等式是不是很眼熟呢。我們回憶在最短路的學習中,是不是有dis[v] >=dis[u] + k;這個不等式組呢。此不等式組在最短路中的意義爲:
到v的最短路>=到u的最短路+(u到v的比邊權)。
我們可以注意到u到v的邊權爲k。
那麼我們是不是可以像最短路一樣,
給x1到x2連上一條邊權爲-k的邊呢。
我們通過此種建圖之後,如何繼續判斷是否存在一組解滿足不等式組呢?
我們綜合最短路的應用,我們是不是可以跑最短路spfa算法,來判斷,該圖中是否存在負環,如果存在負環,那麼一定是無解的。
存在負環的情況爲:a>b,b>c,c>a。可畫圖,就可以理解爲何存在負環不行。

例題:https://www.luogu.com.cn/problem/P1993

#include"stdio.h"
#include"string.h"
#include"algorithm"
using namespace std;

const int N = 100100;
const int INF = 1e9 + 7;
int n,m,ans;
int head[N],ver[N * 10],Next[10 * N],edge[N * 10],tot;
int vis[N],dist[N];

void add(int x,int y,int w){
    ver[++ tot] = y; edge[tot] = w;
    Next[tot] = head[x]; head[x] = tot;
}
void dfs(int x){
    vis[x] = 1;
    for(int i = head[x]; i; i = Next[i])
    {
        int y = ver[i],w = edge[i];
        if(dist[y] > dist[x] + w){
            if(vis[y] || ans) {ans = 1;break;}
            dist[y] = dist[x] + w;
            dfs(y);
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i ++)
        dist[i] = INF;
    for(int i = 1; i <= m; i ++)
    {
        int op; scanf("%d",&op);
        if(op == 3) {
            int x,y; scanf("%d%d",&x,&y);
            add(x,y,0); add(y,x,0);
        } else if(op == 1){
            int x,y,w; scanf("%d%d%d",&x,&y,&w);
            add(x,y,-w);
        } else {
            int x,y,w; scanf("%d%d%d",&x,&y,&w);
            add(y,x,w);
        }
    }
    ans = 0;
    for(int i = 1; i <= n; i ++){
        memset(vis,0,sizeof(vis));
        dfs(i);
        if(ans) break;
    }
    if(ans == 1) printf("No\n");
    else printf("Yes\n");
}

例題二:https://www.luogu.com.cn/problem/P3275
參考題解:https://www.luogu.com.cn/problemnew/solution/P3275
從題目中,我們可以得到所有的小朋友一定會分到一個糖果。
同時,題目要求的是最小需要多少個糖果才能滿足需求。
我們現在建圖時,建(u,v,w)這條邊表示頂點v比頂點u大w。
我們觀察5種需求類型,
第一種,a==b,add(u,v,0);add(v,u,0);
第二種,a<b add(b,a,1) //如果b大於a,那麼我要滿足使最後總糖果數最少,根據貪心原則,我肯定是儘可能的使得b靠近a,即b == a+1。
第三種,a>=b add(b,a,0)//如果a>=b,那麼同樣根據貪心原則,我肯定是讓他們越靠近越好。
第四種,a>b add(b,a,1);
第五種,a<=b add(a,b,0);
那麼我們建圖之後就跑最長路即可。
如下圖,
在這裏插入圖片描述
3到1的路徑爲1,但是因爲2的限制,所以1要比3大2。
同時,在跑spfa的時候判正權環的出現。
正權環即:
1->2 w 2
2->3 w 3
3->1 w 3
如果在此圖上跑最長路,那麼我們將會一直跑下去,不能結束,所以要判斷正權環。

#include"stdio.h"
#include"string.h"
#include"queue"
#include"algorithm"
using namespace std;
typedef long long ll;
inline int read(){
    int a=0;char x=getchar();
    while(x<'0'||x>'9')x=getchar();
    while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+x-48,x=getchar();
    return a;
}
const int N = 300100,M = 1001010;
int head[N],ver[M],Next[M],edge[M],tot;
int n,k,dist[N],vis[N];
int cnt[N];
void add(int x,int y,int w){
    ver[++ tot] = y; Next[tot] = head[x];
    edge[tot] = w; head[x] = tot;
}

bool bfs()
{
    queue<int> Q; Q.push(0);
    dist[0] = 0;vis[0] = 1;
    while(!Q.empty())
    {
        int x = Q.front(); Q.pop();
        vis[x] = 0;
        cnt[x] ++;
        if(cnt[x] >= n){
            return false;
        }
        for(int i = head[x]; i; i = Next[i]){
            int y = ver[i],w = edge[i];
            if(dist[y] < dist[x] + w){
                dist[y] = dist[x] + w;
                if(vis[y] == 0){
                    vis[y] = 1; Q.push(y);
                }
            }
        }
    }
    return true;
}
int main()
{
    n = read(); k = read();
    for(int i = 1; i <= k; i ++)
    {
        int op,a,b;
        op = read(); a = read(); b = read();
        if(op == 1) add(a,b,0),add(b,a,0);
        else if(op == 2) add(a,b,1);
        else if(op == 3) add(b,a,0);
        else if(op == 4) add(b,a,1);
        else add(a,b,0);
        if(op == 2 || op == 4)
        {
            if(a == b) {printf("-1\n"); return 0;}
        }
    }
    for(int i = n; i >= 1; i --)add(0,i,1);
    if(bfs() == false){
        printf("-1\n");
    } else
    {
        ll ans = 0;
        for(int i = 1; i <= n; i ++) ans += (ll)dist[i];
        printf("%lld\n",ans);
    }
}
:

例題三:https://www.cnblogs.com/five20/p/9173155.html
例題:https://www.luogu.com.cn/problem/P2294
很容易的想到建圖方式:
w[u,v] = k,表示v大u,k個單位。
同時隱含着w[v,u] = -k。這一條件。
把這兩個圖建出來,發現,如果有環,那就不合法。

#include"stdio.h"
#include"string.h"
#include"queue"
#include"algorithm"
using namespace std;
typedef long long ll;
inline  int read(){
    int a=0;char x=getchar();bool f=0;
    while((x<'0'||x>'9')&&x!='-')x=getchar();
    if(x=='-')x=getchar(),f=1;
    while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+x-48,x=getchar();
    return f?-a:a;
}
const int N = 300100,M = 1001010;
const int INF = 1000000100;
int head[N],ver[M],Next[M],edge[M],tot;
int n,k,dist[N],vis[N];
int cnt[N];

void add(int x,int y,int w)
{
    ver[++ tot] = y;
    Next[tot] = head[x];
    edge[tot] = w;
    head[x] = tot;
}

bool bfs(int x)
{
    queue<int> Q;
    Q.push(x);
    for(int i = 0; i <= n; i ++) dist[i] = -INF;
    dist[x] = 0;
    vis[x] = 1;
    while(!Q.empty())
    {
        int x = Q.front();
        Q.pop();
        vis[x] = 0;
        cnt[x] ++;
        if(cnt[x] >= n)
        {
            return false;
        }
        for(int i = head[x]; i; i = Next[i])
        {
            int y = ver[i],w = edge[i];
            if(dist[y] < dist[x] + w)
            {
                dist[y] = dist[x] + w;
                if(vis[y] == 0)
                {
                    vis[y] = 1;
                    Q.push(y);
                }
            }
        }
    }
    return true;
}
void init()
{
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(cnt,0,sizeof(cnt));
    tot = 0;
}
int main()
{
    int T;
    T = read();
    while(T --)
    {
        init();
        n = read();
        k = read();
        for(int i = 1; i <= k; i ++)
        {
            int a,b,w;
            a = read(); b = read();w = read();
            add(a - 1,b,w); add(b,a - 1,-w);
        }
        int mark = 1;
        for(int i = 0; i <= n; i ++){
            if(cnt[i] == 0){
                if(bfs(i) == false) {mark = 0;break;}
            }
        }
        if(mark == 1) printf("true\n");
        else printf("false\n");
    }
    return 0;
}

發佈了358 篇原創文章 · 獲贊 43 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章