差分约束

差分约束

  差分约束是一种非常神奇的算法,其实也可以说是一种最短路的建模技巧。这个算法其实学过一次,但是又忘掉了...所以这次记一记。

  比如给出一些不等式:$a_i-a_j<=c$,要求判断是否有解,或者是求最小的解。第一次见到这种题的时候感觉无从下手,而且洛谷上还标着“图论”的标签。所以说是数形结合。让我们把这个式子化一化:

  进行移项,成为$a_i<=a_j+c$,令$c=w(j,i)$,就得到:$d[i]<=d[j]+w(j,i)$,非常像最短路的松弛。如果式子出现变化,通过一些移项,取相反数化为一般的样子就可以啦。注意如果两个变量相等要连双向边。接下来的做法就取决于题目了,下面说说我见过的几种:

  1.SPFA找负环判断是否有解;

  2.图有可能会不连通,这时候虚构一个源点向所有其他点连一些边,具体怎么连看题目要求;

  3.求解的数目...刚刚发现这种情况是不存在的,因为差分约束要么无解,要么有无数组解(已有的解全部加上一个常数);

  4.最短路:求$a_n-a_1$的最大值,这里看起来非常不符合常理,但是是正确的。最短路的松弛操作是这样的:

 1 if(d[beg]+g[i].co>d[j])
  2     d[j]=d[beg]+g[i].co;

  事实上最短路的解也就是要求对于所有的beg,j达到d[beg]+g[i].co<=d[j]的效果,所以说最短路更新出来的就是差分约束的最大值;

  5.最长路:和4的道理差不多;

   

  真是令人非常开心啊,现在可以看题了。

   小K的农场:https://www.luogu.org/problemnew/show/P1993

  写了这个blog之后对差分约束更明白了,这题就是个板子题,只需要判断是否有解就可以了。写这道题的时候建图思路很清奇啊,和上面讲的完全不是一个写法。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # include <cstring>
 4 # include <queue>
 5 
 6 using namespace std;
 7 
 8 struct edge
 9 {
10     int nex,too,co;
11 }g[10009];
12 
13 int a,b,c,n,h=0,m,op,x,y,k,firs[10007],d[10007],s[10007];
14 bool f=false,vis[10007],in_q[10009]={false};
15 queue <int> q;
16 
17 void add(int x,int y,int co)
18 {
19     g[++h].co=co;
20     g[h].too=y;
21     g[h].nex=firs[x];
22     firs[x]=h;
23 }
24 
25 void dfs(int x)
26 {
27     int j;
28     vis[x]=true;
29     for (int i=firs[x];i;i=g[i].nex)
30     {
31         j=g[i].too;
32         if(d[j]>d[x]+g[i].co)
33         {
34             if(vis[j])
35             {
36                 f=true;
37                 return ;
38             }
39             d[j]=d[x]+g[i].co;
40             dfs(j);
41         }
42     }
43     vis[x]=false;
44     return ;
45 }
46 int main()
47 {
48     scanf("%d%d",&n,&m);
49     for (int i=1;i<=m;i++)
50     {
51         scanf("%d%d%d",&op,&a,&b);
52         if(op==1)
53         {
54             scanf("%d",&c);
55             add(a,b,-1*c);
56         }
57         else if(op==2)
58         {
59             scanf("%d",&c);
60             add(b,a,c);
61         }
62         else if(op==3)
63         {
64             add(a,b,0);
65             add(b,a,0);
66         }
67     }
68     for (int i=1;i<=n;i++)
69     {
70         d[i]=0;
71         dfs(i);
72         if(f) break;
73     }
74     if(f)  printf("No"); else printf("Yes");
75     return 0;
76 }
小K的农场

   

  [SCOI2011]糖果:https://www.luogu.org/problemnew/show/P3275

   这题堪称大杂烩,有五种操作,思维要清晰一点,多写写画画。因为要求分到的糖最少,所以求最长路,注意每个小朋友都必须有糖吃才行。

  
 1 // luogu-judger-enable-o2
 2 # include <cstdio>
 3 # include <iostream>
 4 # include <queue>
 5 # include <cstring>
 6 # define R register int
 7 
 8 int n,k,firs[100009],h,a,b,x,d[100009],t[100009];
 9 bool vis[100009];
10 std::queue <int> q;
11 long long ans=0;
12 struct edge
13 {
14     int too,co,nex;
15 }g[2000009];
16 
17 int read()
18 {
19     int x=0;
20     char c=getchar();
21     while (!isdigit(c))
22         c=getchar();
23     while (isdigit(c))
24         x=(x<<3)+(x<<1)+(c^48),c=getchar();
25     return x;    
26 }
27 
28 void add (int x,int y,int co)
29 {
30     g[++h].co=co;
31     g[h].too=y;
32     g[h].nex=firs[x];
33     firs[x]=h;
34 }
35 
36 int main()
37 {
38     n=read();
39     k=read();
40     for (R i=1;i<=k;++i)
41     {
42         x=read(),a=read(),b=read();
43         if(x==1) add(a,b,0),add(b,a,0);
44         else if(x==2) add(a,b,1);
45         else if(x==3) add(b,a,0);
46         else if(x==4) add(b,a,1);
47         else if(x==5) add(a,b,0);
48         if(a==b&&(x==2||x==4))
49         {
50             printf("-1");
51             return 0;
52         }
53     }
54     for (R i=n;i>=1;--i)
55         add(0,i,1);
56     vis[0]=true;
57     q.push(0);
58     int beg,j;
59     while (q.size())
60     {
61         beg=q.front();
62         q.pop();
63         vis[beg]=false;
64         if(t[beg]==n-1) 
65         {
66             printf("-1");
67             return 0;
68         }
69         t[beg]++;
70         for (R i=firs[beg];i;i=g[i].nex)
71         {
72             j=g[i].too;
73             if(d[beg]+g[i].co>d[j])
74             {
75                 d[j]=d[beg]+g[i].co;
76                 if(vis[j]==false) vis[j]=true,q.push(j);
77             }
78         }
79     }
80     for (int i=1;i<=n;++i)
81         ans+=d[i];
82     printf("%lld",ans);
83     return 0;
84 }
糖果

 

  学算法还是重在理解啊,之前没理解的时候只能靠举例子来猜是最长路还是最短路,是从a到b连边还是反过来,现在就不用纠结这些了。

  [HNOI2005]狡猾的商人:https://www.luogu.org/problemnew/show/P2294

  题意概述:给出一些关于区间和的信息,判断是否有冲突。

  区间和看起来不是很好维护,但是不要忘了还有前缀和这种奇妙的东西,如果说$a[i]+a[i+1]...+a[j]=c$,可以化为这个样子:$s[j]-s[i-1]=c$,再拆成两个不等式连边,$s[t]<=s[s-1]+w,s[s-1]<=s[t]-w$,最后SPFA找负环判断可行性。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # include <queue>
 4 # include <cstring>
 5 # define R register int
 6 
 7 int S,T,v,w,n,m,h,firs[109],vis[109],t[109],d[109];
 8 struct edge
 9 {
10     int too,nex,co;
11 }g[100009];
12 std::queue <int> q;
13 bool f;
14 
15 void add (int x,int y,int co)
16 {
17     g[++h].too=y;
18     g[h].nex=firs[x];
19     firs[x]=h;
20     g[h].co=co;
21 }
22 
23 int main()
24 {
25     scanf("%d",&w);
26     while (w--)
27     {
28         f=true;
29         memset(d,0,sizeof(d));
30         memset(vis,0,sizeof(vis));
31         memset(t,0,sizeof(t));
32         memset(firs,0,sizeof(firs));
33         h=0;
34         scanf("%d%d",&n,&m);
35         for (R i=1;i<=m;++i)
36         {
37             scanf("%d%d%d",&S,&T,&v);
38             add(T,S-1,v);
39             add(S-1,T,-v);
40         }
41         for (R i=1;i<=n;++i)
42             add(n+1,i,1);
43         while (q.size()) q.pop();
44         q.push(n+1);
45         vis[n+1]=true;
46         int beg,j;
47         while (q.size())
48         {
49             beg=q.front();
50             q.pop();
51             vis[beg]=false;
52             if(t[beg]==n-1) 
53             {
54                 f=false;
55                 break;
56             }
57             t[beg]++;
58             for (R i=firs[beg];i;i=g[i].nex)
59             {
60                 j=g[i].too;
61                 if(d[beg]+g[i].co>d[j])
62                 {
63                     d[j]=d[beg]+g[i].co;
64                     if(vis[j]==false) vis[j]=true,q.push(j);
65                 }
66             }
67         }
68         if(f) printf("true
");
69         else printf("false
");
70     }
71     return 0;
72 }
狡猾的商人

 


 

  bzoj1731 layout:https://www.lydsy.com/JudgeOnline/problem.php?id=1731

   题意概述:数轴上有一排数,给出一些不等式,求$a_n-a_1$的最大值。

  这道题本身不是很难,但是有一些思想是比较重要的。题目里面往往隐含了一些条件,不要忘记把他们加进差分约束系统中去。这道题提到数字是按照不降序排列的,所以可以得到$a_{i}-a_{i-1}>=0$这样的形式。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # include <cstring>
 4 # include <queue>
 5 # define R register int
 6 # define inf 1000000009
 7 
 8 int a,b,n,h,ml,md,firs[1009],t[1009];
 9 long long co,d[1009];
10 std::queue <int> q;
11 bool vis[1009],inq[1009];
12 struct edge
13 {
14     int too,nex;
15     long long co;
16 }g[30009];
17 
18 void add (int x,int y,long long co)
19 {
20     g[++h].co=co;
21     g[h].too=y;
22     g[h].nex=firs[x];
23     firs[x]=h;
24 }
25 
26 int main()
27 {
28     scanf("%d%d%d",&n,&ml,&md);
29     for (R i=1;i<=ml;++i)
30     {
31         scanf("%d%d%lld",&a,&b,&co);
32         add(a,b,co);
33     }
34     for (int i=1;i<=md;++i)
35     {
36         scanf("%d%d%lld",&a,&b,&co);
37         add(b,a,-co);
38     }
39     for (int i=2;i<=n;++i)
40         add(i,i-1,0);
41     q.push(1);
42     int j,beg;
43     for (int i=1;i<=n;++i)
44         d[i]=inf;
45     d[1]=0;
46     while (q.size())
47     {
48         beg=q.front();
49         q.pop();
50         vis[beg]=false;
51         if(t[beg]==n-1)
52         {
53             printf("-1");
54             return 0;
55         }
56         t[beg]++;
57         for (int i=firs[beg];i;i=g[i].nex)
58         {
59             j=g[i].too;
60             if(d[beg]+g[i].co<d[j]) 
61             {
62                 d[j]=d[beg]+g[i].co;
63                 if(!vis[j]) vis[j]=true,q.push(j);
64             }
65         }
66     }
67     if(d[n]==inf) printf("-2");
68     else printf("%lld",d[n]);
69     return 0;
70 }
layout

   


  

  [USACO13OPEN]照片Photo:https://www.luogu.org/problemnew/show/P3084

  题意概述:给定m个包含于[1,n]的区间,要求每个区间有且只有一个特殊点,判断是否有解,如果有,输出最多有多少个特殊点。$1<= M <=100,000,1<= N <=200,000$

  一般这种涉及到区间的就可以考虑用前缀和搞一搞,因为每个点必然是0或1,所以前缀和隐含一个条件$0<=s[i]-s[i-1]<=1$.把前缀和按题意连一连就好了。不过这题太神了,不仅要判负环,还卡SPFA...学了一个新的技巧,用堆优化SPFA,有的时候其实还是快的,可以更快的更新出最小值来,但是因题而异。题解表示...如果快超时了就认为有负环,感觉海星。

  什么是堆优化SPFA呢?就是把堆优化dij的vis数组去掉再加上in_que数组。题解还说这题可以dp,感觉非常神仙。

  
 1 // luogu-judger-enable-o2
 2 # include <cstdio>
 3 # include <iostream>
 4 # include <queue>
 5 # include <cstring>
 6 # include <ctime>
 7 # define R register int
 8 
 9 using namespace std;
10 
11 const int maxn=200009;
12 const int maxm=1000009;
13 int n,x,h,y,m,firs[maxn],d[maxn],t[maxn];
14 typedef pair <int,int> pii;
15 priority_queue <pii,vector<pii>,greater<pii> > q;
16 bool in_que[maxn],f;
17 struct edge
18 {
19     int too,nex,co;    
20 }g[maxm];
21 
22 int read()
23 {
24     int x=0;
25     char c=getchar();
26     while (!isdigit(c))
27         c=getchar();
28     while (isdigit(c))
29     {
30         x=(x<<3)+(x<<1)+(c^48);
31         c=getchar();    
32     }
33     return x;
34 }
35 
36 void add (int x,int y,int co)
37 {
38     g[++h].too=y;
39     g[h].nex=firs[x];
40     firs[x]=h;
41     g[h].co=co;
42 }
43 
44 int main()
45 {    
46 
47     scanf("%d%d",&n,&m);
48     for (R i=1;i<=m;++i)
49     {
50         x=read();
51         y=read();
52         if(x>y) swap(x,y);
53         add(y,x-1,-1);
54         add(x-1,y,1);
55     }
56     for (R i=1;i<=n;++i)
57         add(i,i-1,0),add(i-1,i,1);
58     memset(d,0x3f,sizeof(d));
59     d[0]=0;
60     q.push(make_pair(0,0));
61     int beg,j;
62     while (q.size())
63     {
64         if ((float)clock() / CLOCKS_PER_SEC > 0.9)
65         {
66             f=true;
67             break;
68         }
69         beg=q.top().second;
70         q.pop();
71         in_que[beg]=false;
72         if(t[beg]==n-1)
73         {
74             f=true;
75             break;
76         }
77         t[beg]++;
78         for (R i=firs[beg];i;i=g[i].nex)
79         {
80             j=g[i].too;
81             if(d[j]>d[beg]+g[i].co)
82             {
83                 d[j]=d[beg]+g[i].co;
84                 if(!in_que[j]) in_que[j]=true,q.push(make_pair(d[j],j)); 
85             }
86         }
87     }
88     if(f) printf("-1");
89     else printf("%d",d[n]); 
90     return 0;
91 }
照片Photo

  ---shzr

原文地址:https://www.cnblogs.com/shzr/p/9350457.html