差分约束系统小结
百科是这样说的:如果一个系统由n个变量和m个约束条件组成,其中每个约束条件形如xj-xi<=bk(i,j∈[1,n],k∈[1,m]),则称其为差分约束系统。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
学习差分约束系统之前,必须掌握单源最短路径的算法(Bellman-ford、SPFA)。习惯上用SPFA解差分约束的题目(其实SPFA是由Bellman-ford优化而来,看代码可以知道Bellman-ford的dist[v]需要松弛n-1次,SPFA在发现dist[v]没有变化后就不再进行松弛了)。
解题方法有两种:
假设j > i,
1、由xj - xi >=w得到xj >= xi + w,建立一条从xi->xj的边权为w的边。然后从小到大求最长路算法。
2、由xj - xi >=w得到xi <= xj - w,建立一条从xj->xi的边权为-w的边。然后从大到小求最短路算法。
实际上,最长路和最短路算法的区别很小(SPFA模版)。
最长路算法中dist[]初始化为负无穷,松弛的时候是if(dist[v] < dist[u] + Map[u][v]) 。
最短路算法中dist[]初始化为正无穷,松弛的时候是if(dist[v] > dist[u] + Map[u][v]) 。
为了方便记忆以及做题,就以最短路模型来详细讲解:
对于每一个未知量xi,都可以看作是图中的一个顶点。每一个不等式看作图中的一条有向边。对于每一个差分约束xj - xi <=bk(记住是小于等于),建立边<x1,xj>,权值为bk。用小于等于来建边是为了可以用最短路的方法来解。可以这样理解,xi,xj是X轴正向上的点,bk都是正的值,如果知道了xi的值,那么从约束条件 xj - xi <=bk可以知道xj的上限,求xj的最小值其实就是求xi到xj的最短距离。这样能理解,就有思路了。这样建边还是不够的,我们并不能保证图是连通的,可以建立一个超级源点s,s到每个点都有一条权值为0边。当然,有些题目并没有这么简单,有一些隐含条件,需要用来建边。
需要注意的是,图中存在负环,则该差分约束就无解。当s到vi的最短距离为INF的时候,说明该点对应的变量可以任意取值。
下面是具体的题目:
hdu1384
题目大意是:
给出一些区间[ai,bi]和每个区间最少需要ci个点,然后问总共最少需要几个点满足所有区间的要求。
由于1<=ai<=bi,所以ai-1或者bi+1都是没关系的,都是可以的。(下标从0开始和1开始都行,却不能是负数)
设s[i]是1~i 中需要的点的个数。则s[j+1]-s[i]>=w。这只是题目给出的数据所能建的图。为什么是j+1而不是j,闭区间[i,j]之间有j-i+1个整数点,我们建图也需要在他们之间有那么多个点。
但是有很多点是没有连通的。根据隐含条件 0=<s[i+1] - s[i] <=1,可以对每个点都再建两条边。
这样就可以做题了。
如果选择建立xj >= xi + w的关系来建图,就需要用最长路经来解题。dist[]初始化为负无穷。
PS:这题不能用建立超级源点的方法来做。
具体的看代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #include<algorithm> 6 using namespace std; 7 8 const int N = 50010, M=150010; 9 const int INF = 0x3f3f3f3f; 10 struct node 11 { 12 int to, w, next; 13 }; 14 node edge[M]; 15 int head[N], dist[N], outq[N]; 16 bool vis[N]; 17 int tot; 18 bool SPFA(int s, int n ) 19 { 20 int i,k; 21 for(i=0;i<=n;i++) dist[i]=-INF; 22 memset(vis,0,sizeof(vis)); 23 memset(outq,0,sizeof(outq)); 24 queue<int > q; 25 while(!q.empty()) q.pop(); 26 vis[s]=1; 27 dist[s]=0; 28 q.push(s); 29 while(!q.empty()) 30 { 31 int u=q.front(); 32 q.pop(); 33 vis[u]=0; 34 outq[u]++; 35 if(outq[u]>n) return 0 ; 36 k=head[u]; 37 while(k>=0) 38 { 39 if(dist[edge[k].to]-edge[k].w<dist[u]) 40 { 41 dist[edge[k].to]=dist[u]+edge[k].w; 42 if(!vis[edge[k].to]) 43 { 44 vis[edge[k].to]=1; 45 q.push(edge[k].to); 46 } 47 } 48 k=edge[k].next; 49 } 50 } 51 return 1; 52 } 53 void addedge(int i, int j ,int w) 54 { 55 edge[tot].to=j; 56 edge[tot].w=w; 57 edge[tot].next=head[i]; 58 head[i]=tot++; 59 } 60 void init() 61 { 62 memset(head,-1,sizeof(head)); 63 tot=0; 64 } 65 int main() 66 { 67 //freopen("test.txt","r",stdin); 68 int n,m,i,j,k,w,a,b; 69 while(scanf("%d",&m)!=EOF) 70 { 71 init(); 72 a=INF,b=-INF; 73 for(k=0;k<m;k++) 74 { 75 scanf("%d %d %d",&i,&j,&w); 76 if(a>i) a=i; 77 if(b<j+1) b=j+1; 78 addedge(i,j+1,w); 79 } 80 for(i=a;i<=b;i++) 81 { 82 addedge(i,i+1,0); 83 addedge(i+1,i,-1); 84 } 85 if(SPFA(a,b)) printf("%d\n",dist[b]); 86 } 87 return 0; 88 }
hdu 1534 Schedule Problem
题目大意:
某一任务的分为n个部分,给出完成每一部分所需要的时间以及某些部分任务之间的四种约束。求每个部分的开始的时间。
思路:
以求最长路的方法来做。
设p[i]是完成i所需要的时间,这是已知的。设s[i]为任务i开始的时间。
SAS addedge(j, i, 0); 可以表示为s[i]>=s[j] 。任务i在任务j开始以后才开始。
SAF addedge(j, i, p[j]); 可以表示为s[i]>=s[j] + p[j] 。任务i在任务j完成以后才开始。
FAS addedge(j, i, -p[i]); 可以表示为s[i]>=s[j] - p[i] 。任务i在任务j开始以后才完成。
FAF addedge(j, i ,p[j]-p[i]); 可以表示为s[i]>=s[j] +p[j] - p[i] 。任务i在任务j完成以后才完成。
这样,题目给出的条件已经建边了。但是图可能不连通,所以需要建立超级源点。比如说,输入为SAS 1 2 SAF 3 4 。 以1为源点的话图是不连通的。为什么超级源点是合理的呢?因为1和3是可以同时开始的,他们并没有约束。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #include<algorithm> 6 using namespace std; 7 8 const int N = 50010, M=150010; 9 const int INF = 0x3f3f3f3f; 10 struct node 11 { 12 int to, w, next; 13 }; 14 node edge[M]; 15 int head[N], dist[N], outq[N]; 16 bool vis[N]; 17 int tot; 18 bool SPFA(int s, int n ) 19 { 20 int i,k; 21 for(i=0;i<=n;i++) dist[i]=-INF; 22 memset(vis,0,sizeof(vis)); 23 memset(outq,0,sizeof(outq)); 24 queue<int > q; 25 while(!q.empty()) q.pop(); 26 vis[s]=1; 27 dist[s]=0; 28 q.push(s); 29 while(!q.empty()) 30 { 31 int u=q.front(); 32 q.pop(); 33 vis[u]=0; 34 outq[u]++; 35 if(outq[u]>n) return 0 ; 36 k=head[u]; 37 while(k>=0) 38 { 39 if(dist[edge[k].to]-edge[k].w<dist[u]) 40 { 41 dist[edge[k].to]=dist[u]+edge[k].w; 42 if(!vis[edge[k].to]) 43 { 44 vis[edge[k].to]=1; 45 q.push(edge[k].to); 46 } 47 } 48 k=edge[k].next; 49 } 50 } 51 return 1; 52 } 53 void addedge(int i, int j ,int w) 54 { 55 edge[tot].to=j; 56 edge[tot].w=w; 57 edge[tot].next=head[i]; 58 head[i]=tot++; 59 } 60 void init() 61 { 62 memset(head,-1,sizeof(head)); 63 tot=0; 64 } 65 int main() 66 { 67 //freopen("test.txt","r",stdin); 68 int n,m,i,j,k,w,a,b; 69 while(scanf("%d",&m)!=EOF) 70 { 71 init(); 72 a=INF,b=-INF; 73 for(k=0;k<m;k++) 74 { 75 scanf("%d %d %d",&i,&j,&w); 76 if(a>i) a=i; 77 if(b<j+1) b=j+1; 78 addedge(i,j+1,w); 79 } 80 for(i=a;i<=b;i++) 81 { 82 addedge(i,i+1,0); 83 addedge(i+1,i,-1); 84 } 85 if(SPFA(a,b)) printf("%d\n",dist[b]); 86 } 87 return 0; 88 }
hdu3440 House Man
题目大意:(我自己说不清楚,拷贝自 http://www.cnblogs.com/scau20110726/archive/2013/05/04/3059625.html)
有n个屋子,超人从最矮的屋子开始,依次跳下比当前屋子高且最接近当前高度的屋子(即按照屋子高度增序来跳),但超人跳跃还有一个水平距离限制D, 他每次跳的水平距离<=D。现在给你每个屋子的高度是它们的相对位置,你不能改变屋子的相对位置,但是可以水平移动屋子,使得最矮的屋子和最高的屋子的水平距离最大。如果无论怎样移动,超人都无法跳到最后那个屋子则输出-1。
思路和建图:
建立一个结构体,存放房子的高度和标号。按照房子高度排序。建图规则,任意高度(直接)相邻的房子之间的距离满足 1<=dist[i][j]<=D 。
注意:建图的规则不能错。如果把相邻两点理解为任意两点之间的距离就错了。(惭愧,我就这样错了!)
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<cstring> 5 #include<algorithm> 6 using namespace std; 7 const int N = 1010, M=10010; 8 const int INF = 0x3f3f3f3f; 9 struct node 10 { 11 int to, w, next; 12 }; 13 node edge[M]; 14 int head[N], dist[N], outq[N]; 15 bool vis[N]; 16 int tot,D; 17 bool SPFA(int s, int n ) 18 { 19 int i,k; 20 for(i=0;i<=n;i++) dist[i]=INF; 21 memset(vis,0,sizeof(vis)); 22 memset(outq,0,sizeof(outq)); 23 queue<int > q; 24 while(!q.empty()) q.pop(); 25 vis[s]=1; 26 dist[s]=0; 27 q.push(s); 28 while(!q.empty()) 29 { 30 int u=q.front(); 31 q.pop(); 32 vis[u]=0; 33 outq[u]++; 34 if(outq[u]>n) return 0 ; 35 k=head[u]; 36 while(k>=0) 37 { 38 if(dist[edge[k].to]-edge[k].w>dist[u]) 39 { 40 dist[edge[k].to]=dist[u]+edge[k].w; 41 if(!vis[edge[k].to]) 42 { 43 vis[edge[k].to]=1; 44 q.push(edge[k].to); 45 } 46 } 47 k=edge[k].next; 48 } 49 } 50 return 1; 51 } 52 void addedge(int i,int j,int w) 53 { 54 edge[tot].to=j; 55 edge[tot].w=w; 56 edge[tot].next=head[i]; 57 head[i]=tot++; 58 } 59 void init() 60 { 61 tot=0; 62 memset(head,-1,sizeof(head)); 63 } 64 struct house 65 { 66 int id,d; 67 }p[N]; 68 bool cmp(const house &a, const house &b) 69 { 70 return a.d < b.d; 71 } 72 int main() 73 { 74 //freopen("test.txt","r",stdin); 75 int n,i,j,k,t=0,cas; 76 scanf("%d",&cas); 77 while(cas--) 78 { 79 scanf("%d%d",&n,&D); 80 init(); 81 for(k=0;k<n;k++) 82 { 83 scanf("%d",&p[k].d); 84 p[k].id=k+1; 85 } 86 sort(p,p+n,cmp); 87 for(i = 1; i < n; i ++){ 88 addedge(i+1, i, -1); 89 addedge(min(p[i].id, p[i - 1].id), max(p[i].id, p[i - 1].id), D); 90 } 91 printf("Case %d: ",++t); 92 int u=min(p[0].id,p[i-1].id),v=max(p[0].id,p[i-1].id); 93 if(SPFA(u,n)) printf("%d\n",dist[v]); 94 else printf("-1\n"); 95 } 96 return 0; 97 }
上面的一个是求最短路的代码。下面是求最大路的代码。看得懂了,基本上就会查分约束了。
hdu 3440
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<cstring> 5 #include<algorithm> 6 using namespace std; 7 const int N = 1010, M=10010; 8 const int INF = 0x3f3f3f3f; 9 struct node 10 { 11 int to, w, next; 12 }; 13 node edge[M]; 14 int head[N], dist[N], outq[N]; 15 bool vis[N]; 16 int tot,D; 17 bool SPFA(int s, int n ) 18 { 19 int i,k; 20 for(i=0;i<=n;i++) dist[i]=-INF; 21 memset(vis,0,sizeof(vis)); 22 memset(outq,0,sizeof(outq)); 23 queue<int > q; 24 while(!q.empty()) q.pop(); 25 vis[s]=1; 26 dist[s]=0; 27 q.push(s); 28 while(!q.empty()) 29 { 30 int u=q.front(); 31 q.pop(); 32 vis[u]=0; 33 outq[u]++; 34 if(outq[u]>n) return 0 ; 35 k=head[u]; 36 while(k>=0) 37 { 38 if(dist[edge[k].to]-edge[k].w<dist[u]) 39 { 40 dist[edge[k].to]=dist[u]+edge[k].w; 41 if(!vis[edge[k].to]) 42 { 43 vis[edge[k].to]=1; 44 q.push(edge[k].to); 45 } 46 } 47 k=edge[k].next; 48 } 49 } 50 return 1; 51 } 52 void addedge(int i,int j,int w) 53 { 54 edge[tot].to=j; 55 edge[tot].w=w; 56 edge[tot].next=head[i]; 57 head[i]=tot++; 58 } 59 void init() 60 { 61 tot=0; 62 memset(head,-1,sizeof(head)); 63 } 64 struct house 65 { 66 int id,d; 67 }p[N]; 68 bool cmp(const house &a, const house &b) 69 { 70 return a.d < b.d; 71 } 72 int main() 73 { 74 //freopen("test.txt","r",stdin); 75 int n,i,j,k,t=0,cas; 76 scanf("%d",&cas); 77 while(cas--) 78 { 79 scanf("%d%d",&n,&D); 80 init(); 81 for(k=0;k<n;k++) 82 { 83 scanf("%d",&p[k].d); 84 p[k].id=k+1; 85 } 86 sort(p,p+n,cmp); 87 for(i = 1; i < n; i ++){ 88 addedge(i, i+1, 1); 89 addedge(max(p[i].id, p[i - 1].id), min(p[i].id, p[i - 1].id), -D); 90 } 91 printf("Case %d: ",++t); 92 int u=max(p[0].id,p[i-1].id),v=min(p[0].id,p[i-1].id); 93 if(SPFA(u,n)) printf("%d\n",-dist[v]); 94 else printf("-1\n"); 95 } 96 return 0; 97 }