差分约束系统小结

百科是这样说的:如果一个系统由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 }
View Code

 

   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 }
View Code

 

  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 }
View Code

 

   上面的一个是求最短路的代码。下面是求最大路的代码。看得懂了,基本上就会查分约束了。

  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 }
View Code

 

 

 

 

posted @ 2014-09-07 00:04  pengmq  阅读(661)  评论(0编辑  收藏  举报