差分约束
差分约束
差分约束是一种非常神奇的算法,其实也可以说是一种最短路的建模技巧。这个算法其实学过一次,但是又忘掉了...所以这次记一记。
比如给出一些不等式:$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 }
[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\n"); 69 else printf("false\n"); 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 }
[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 }
---shzr