Boapath

   :: 首页  :: 新随笔  ::  ::  :: 管理

  差分约束的题目一般是:给一些约束条件(一些不等式),要求满足所有条件的一个解(最大或最小)。

 

  对于xi - xj ≤ ki;这样的一个不等式,想到求最短路时的松弛技术(d[u]>d[v]+map[v][u])。

 

  所以是否可以将差分约束转化为最短路的问题呢?

  答案是肯定的。

  

  xi,xj 对应于点u,v;ki对应于两点间的距离。

 

但是有几点要注意:

  1、 关于松弛技术,是对于约束:d[u]≤d[v]+w[v,u] 的松弛,等号不能漏——具体证明参见《算导》;

    所以当题目给出的约束条件为不等号>|<时,要记得转化(>k == ≥k+1 || <k == ≤ k-1)。

  2、 (xi - xj) ≠ (xj - xi) 所以构造出来的图是有向图。

  3、 全部转化为d[u]≤d[v]+w[v,u] 这种形式后,可能有负权边,所以不能用Dijkstra。

    Bellman-ford, SPFA都可以。推荐SPFA。

  5、 当图有负权环时,也就不存在满足约束条件的解。否则,SPFA的解就是要求的解。

    6、最短路还是最长路: 所有的约束转化为"<="是求最短,转化为">="就是求最长。(不要忘记等号!)——具体理由见POJ1201的分析。

  7、差分约束的很多题目都有隐含条件,如果不加这些隐含条件图可能不连通。当图可能不连通的时候,去找找有没有漏隐含条件。

    解决图不连通的一个方法就是设立超级源点:

 

    当然,首先要说的是:不是所有题目都要有超级源点。

 

       比方POJ1364:由于我们要确定所有的不等式都要成立,即对于每个顶点都要求一次,这样很烦。而且最重要的是,这个题目没有给出其他的的隐含条件,也就是说单靠题目的条件构图,图可能是不连通的,所以也需要超级源点保持图的连通性。

    一般超级源点有两种方法:

 

    a. 超级源点——其实就是加一个x0点,使得这个点和所有点都相连,并且边权为0,然后从这个点进行SPFA,

 

     这样其他每个顶点均从x0可达。

 

    b.把所有的点一开始就入队,其实加入x0后,根据类似BFS的特性,第一步做的也就是把所有点都入队列。——不过这种方法就不需要x0了。

 

 

总之,差分约束原理和一些构图上的注意点大概就是以上这样了,主要是构图要正确。

 

例子:POJ 1364

首先要指出这个题目并不是很好,因为题目为了卡人,故意写的很绕。 - -

题目的意思是:

  有一个序列S,里面有n个元素——S{a1, a2, ..., an}.;有m个子序列。

  子序列的定义为:Si = {aSi, aSi+1, ..., aSi+ni}。———— 一开始我还以为是题目写错了,应该是ai,事实上这个a和S的ai没有半毛钱关系。

  也就是说其实Si就是一个有ni个元素的连续整数序列,其中第一个整数位aSi,最后一个整数为aSi+ni。(由于所有Si的a都相同,其实就是[Si,Si+ni]。)

  

  输入给出m个子序列的和一个对应ki的大小关系。

  即aSi + aSi+1 + ... + aSi+ni < ki or aSi + aSi+1 + ... + aSi+ni > ki

  设dist[Si+ni]表示从0加到Si+ni的总和:

    对于小于号不等式转化为dist[Si+ni] - dist[Si-1] < ki 

    即 dist[Si+ni] - dist[Si-1] ≤ ki  -1;

  同理 dist[Si+ni] - dist[Si-1] > ki ; 即 dist[Si+ni] - dist[Si-1] ≥ ki+1;即dist[Si-1] - dist[Si+ni] ≤ -ki -1。

 

  我是用超级源点的,题目中Si是从1开始的,所以Si-1是从0开始的,由于我选择0为源点,所以把每个点的标号都再加了1。

  所以一共是1 到 n+1的n+1个点+超级源点 = n+2个点。

 

AC代码如下—— 之前邻接表写错了TLE,SPFA写错了WA。 T T。

 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>
 4 using namespace std;
 5 const int maxn = 111111;
 6 const int INF = 1<<30;
 7 bool visit[maxn];
 8 int dist[maxn];
 9 int vcnt[maxn];//记录入队次数
10 int head[maxn];
11 int xxx;
12 struct  node
13 {
14     int to,w,next;
15 }edge[maxn];
16 void addedge(int from,int to,int w)
17 {
18   edge[xxx].to = to;
19   edge[xxx].w = w;
20   edge[xxx].next = head[from];
21   head[from] = xxx++;
22 }
23 bool spfa(int start,int n)
24 {
25     queue<int> que;
26     memset(visit,false,sizeof visit);
27     memset(vcnt,0,sizeof vcnt);
28     visit[start] = true;
29     vcnt[start] = 1;
30     for(int i = 0;i<111;i++)dist[i] = INF;
31     dist[start] = 0;
32     que.push(start);
33     while(!que.empty())
34     {
35         int top = que.front();
36         que.pop();
37         visit[top] = false;
38         int k = head[top];
39         while(k!=-1)
40         {
41             int v = edge[k].to;
42             if( dist[v] > dist[top] + edge[k].w )
43             {
44                 dist[v] = dist[top] + edge[k].w ;
45                 if(!visit[v])
46                 {
47                     visit[v] = true;
48                     que.push(v);
49                     if(++vcnt[v]>n)return false;
50                 }
51             }
52             k = edge[k].next;
53         }
54     }
55     return true;
56 }
57 int main()
58 {
59   #ifdef LOCALL
60   freopen("in","r",stdin);
61   //freopen("out","w",stdout);
62   #endif
63   char op[5];int n,m;
64   while(scanf("%d",&n),n)
65   {
66     scanf("%d",&m);
67     xxx = 0;
68     memset(head,-1,sizeof head);
69     for(int i = 0;i<m;i++)
70     {
71       int si,ni,ki;
72       scanf("%d%d%s%d",&si,&ni,op,&ki);
73       if(op[0] == 'l'){
74         addedge(si,si+ni+1,ki-1);
75       }
76       else{
77         addedge(si+ni+1,si,-ki-1);
78       }
79     }
80     for(int i = 1;i<n+2;i++)
81     {
82       addedge(0,i,0);
83     }
84 
85     if(spfa(0,n+2))printf("lamentable kingdom\n");
86     else printf("successful conspiracy\n");
87   }
88   return 0;
89 }
poj1364

 

 

 

 

 POJ 3169

题目意思是有很多牛(n个)在一条线上,一些牛之间的距离要至少为ki,一些牛之间的距离最多为kj。问第一头牛和第n头牛的最大的距离。

 it is possible that two or more cows can line up at exactly the same location”这句话提醒两头牛之间的最小距离是0,即(i+1,i) 0。

第一头牛和第n头牛的最大的距离 : 对于 (xi,xj) ki 来说就是求最短路。对于(xi,xj) ki 来说就是求最长路。

AC代码如下:

 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>
 4 using namespace std;
 5 const int maxn = 111111;
 6 const int INF = 0x7fffffff;
 7 int ecnt;
 8 bool visit[maxn];
 9 int dist[maxn];
10 int vcnt[maxn];//记录入队次数
11 int head[maxn];
12 struct  node
13 {
14     int to,w,next;
15 }edge[maxn];
16 void addedges(int from,int to,int w)
17 {
18     edge[ecnt].to = to;
19     edge[ecnt].w = w;
20     edge[ecnt].next = head[from];
21     head[from] = ecnt++;
22 }
23 bool spfa(int start,int n)
24 {
25     queue<int> que;
26     memset(visit,false,sizeof visit);
27     memset(vcnt,0,sizeof vcnt);
28     for(int i = 0;i<=n;i++)dist[i] = INF;
29     dist[start] = 0;
30     visit[start] = true;
31     vcnt[start] = 1;
32     que.push(start);
33     while(!que.empty())
34     {
35         int top = que.front();
36         que.pop();
37         visit[top] = false;
38         int k = head[top];
39         while(k!=-1)
40         {
41             int v = edge[k].to;
42             if( dist[v] > dist[top] + edge[k].w )
43             {
44                 dist[v] = dist[top] + edge[k].w ;
45                 if(!visit[v])
46                 {
47                     visit[v] = true;
48                     que.push(v);
49                     if(++vcnt[v]>n)return false;
50                 }
51             }
52             k = edge[k].next;
53         }
54     }
55     return true;
56 }
57 int main()
58 {
59   int n,ml,md;
60   while(scanf("%d%d%d",&n,&ml,&md)!=EOF)
61   {
62     ecnt = 0;
63     memset(head,-1,sizeof head);
64     for(int i = 0;i<ml;i++)
65     {
66       int a,b,c;
67       scanf("%d%d%d",&a,&b,&c);
68 //      int d = a+b;
69 //      a = a>b?a:b; b = d - a;
70       addedges(a,b,c);
71     }
72     for(int i = 0;i<md;i++)
73     {
74       int a,b,c;
75       scanf("%d%d%d",&a,&b,&c);
76 //      int d = a+b;
77 //      a = a>b?a:b; b = d - a;
78       addedges(b,a,-c);
79     }
80     for(int i = 1;i<=n;i++)
81     {
82       addedges(i+1,i,0);
83     }
84     if(spfa(1,n)) {
85       if(dist[n]<INF)
86         printf("%d\n",dist[n]);
87       else printf("-2\n");}
88     else { printf("-1\n");
89     }
90 
91   }
92   return 0;
93 }
poj 3169

 

 POJ1201

很显然的差分约束,满足“1 <= ci <= bi - ai+1”, 隐藏区间可能是[a,a]的,也就是只有一个元素的。

设dist[i]是覆盖[0,i]区间的满足题意的子集中元素的最小个数。

1、ci <= bi - ai+1, 转化为dist[ai]-dist[bi+1]<=ci。 这里有个细节:题目说“0 <= ai <= bi <= 50000”,本来是转化为dist[ai-1]-dist[bi]<=ci,但是ai可能为0,ai-1就是-1了。这样程序交上去就会RE了。所以把2个点的标号都+1,变成dist[ai]-dist[bi+1]<=ci。

2、就是处理隐藏的区间的问题:1=>dist[i]-dist[i-1]>=0.

即:dist[i-1]-dist[i]<=0  和 dist[i] - dist[i-1] <=1。

然后用SPFA求最短路(要满足所有的<=的性质,只能是最小的——即最短路)。最后输出取反。

 

我看网上有很多人是求最长路的,其实是一样的:因为要满足所有的>=的性质,只能是最大的——即最长路。这时候构图把<=改为>=。然后spfa相应的初值等地方都要改一改。

不过这种方法不用判负环了。

(对于是求最短路,还是最长路就看变换为<=还是>=:如之前所说,d[a]-d[b]<=c对应最短路,d[a]-d[b]>=c对应最长路。)

 

写程序的时候,有几个细节要注意:

1、是上面提到的,ai可能为0的情况。

2、由于题目没给点的标号最大最小是多少,也就是说要自己记录一下,然后由于我们把每个点都+1了,计算的时候要注意不要忘了。

3、开数组的问题,用邻接表的话,记得“边”结构体数组开“点”个数的至少3倍——因为每个相邻点i ~ (i-1) 和 (i-1) ~i 记录了2条边,然后题目还会给n条边。一共最多可能3n条边。

—— WA+RE了几次,就是这些地方处理得不到位。

 AC代码如下:c++和G++均过了。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>
 4 using namespace std;
 5 const int maxn = 55555;
 6 const int INF = 1<<30;
 7 bool visit[maxn];
 8 int dist[maxn];
 9 int vcnt[maxn];//记录入队次数
10 int head[maxn];
11 int xxx;
12 struct  node
13 {
14     int to,w,next;
15 }edge[maxn*3];
16 void addedge(int from,int to,int w)
17 {
18   edge[xxx].to = to;
19   edge[xxx].w = w;
20   edge[xxx].next = head[from];
21   head[from] = xxx++;
22 }
23 bool spfa(int start,int n)
24 {
25     queue<int> que;
26     memset(visit,false,sizeof visit);
27     memset(vcnt,0,sizeof vcnt);
28     visit[start] = true;
29     vcnt[start] = 1;
30     for(int i = start;i<=n;i++)dist[i] = INF;
31     dist[start] = 0;
32     que.push(start);
33     while(!que.empty())
34     {
35         int top = que.front();
36         que.pop();
37         visit[top] = false;
38         int k = head[top];
39         while(k!=-1)
40         {
41             int v = edge[k].to;
42             if( dist[v] > dist[top] + edge[k].w )
43             {
44                 dist[v] = dist[top] + edge[k].w ;
45                 if(!visit[v])
46                 {
47                     visit[v] = true;
48                     que.push(v);
49                     if(++vcnt[v]>(n-start+1))return false;
50                 }
51             }
52             k = edge[k].next;
53         }
54     }
55     return true;
56 }
57 int main()
58 {
59   int n,nm;
60   while(scanf("%d",&n)!=EOF)
61   {
62     xxx = 0;
63     memset(head,-1,sizeof head);
64     nm = 0;
65     int start = 55555;
66     for(int i = 0;i<n;i++)
67     {
68       int a,b,c;
69       scanf("%d%d%d",&a,&b,&c);
70       addedge(a,b+1,-c);
71       nm = nm>(b+1)?nm:(b+1);
72       start = start<(a)?start:(a);
73     }
74     //printf("nm:%d\t,start:%d\n",nm,start);
75     for(int i = start;i<nm;i++)
76     {
77       addedge(i,i+1,0);
78       addedge(i+1,i,1);
79     }
80     if(spfa(start,nm))printf("%d\n",-dist[nm]);
81     else printf("0\n");
82   }
83   return 0;
84 }
POJ1201

 

POJ2983

题目大意:格式P a b a,表示a在b点正北方刚好c的距离;V a b意思是a在b点正北方至少1个距离。

设a在b北方定义为dist[a]-dist[b],那么:

  V a b   即 dist[a] - dist[b] >= 1;

  P a b c   即 dist[a]-dist[b] = c; 即 c =<dist[a]-dist[b] <= c;  

 然后此题也要用到超级源点,因为要判断每个点作为起点有没有负环。题目点的标号从1开始,所以选择0作为超级源点。

用gets读入有空格的字串。

AC代码如下:写完忘记注释掉测试语句WA了一次。然后又是“边”数组一开始开111100——题目说的给出边的数目,忘了自己P的加了2条边,然后超级源点最多加了n条边。也就是说最多加了原来边数的2倍多——RE了一次。开两倍就AC了……

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<queue>
  4 using namespace std;
  5 const int maxn = 1111;
  6 const int INF = 1<<30;
  7 bool visit[maxn];
  8 int dist[maxn];
  9 int vcnt[maxn];//记录入队次数
 10 int head[maxn];
 11 int xxx;
 12 struct  node
 13 {
 14     int to,w,next;
 15 }edge[maxn*200];
 16 void addedge(int from,int to,int w)
 17 {
 18   edge[xxx].to = to;
 19   edge[xxx].w = w;
 20   edge[xxx].next = head[from];
 21   head[from] = xxx++;
 22 }
 23 bool spfa(int start,int n)
 24 {
 25     queue<int> que;
 26     memset(visit,false,sizeof visit);
 27     memset(vcnt,0,sizeof vcnt);
 28     visit[start] = true;
 29     vcnt[start] = 1;
 30     for(int i = start;i<=n;i++)dist[i] = INF;
 31     dist[start] = 0;
 32     que.push(start);
 33     while(!que.empty())
 34     {
 35         int top = que.front();
 36         que.pop();
 37         visit[top] = false;
 38         int k = head[top];
 39         while(k!=-1)
 40         {
 41             int v = edge[k].to;
 42             if( dist[v] > dist[top] + edge[k].w )
 43             {
 44                 dist[v] = dist[top] + edge[k].w ;
 45                 if(!visit[v])
 46                 {
 47                     visit[v] = true;
 48                     que.push(v);
 49                     if(++vcnt[v]>n)return false;
 50                 }
 51             }
 52             k = edge[k].next;
 53         }
 54     }
 55     return true;
 56 }
 57 int main()
 58 {
 59   int n,m;
 60   char line[22];
 61   int a[11];
 62   while(scanf("%d%d",&n,&m)!=EOF)
 63   {
 64     xxx = 0;
 65     memset(head,-1,sizeof head);
 66     char t = getchar();
 67 
 68     for(int i = 0;i<m;i++)
 69     {
 70       gets(line);
 71       //printf("%s\n",line);
 72       int len = strlen(line);
 73       memset(a,0,sizeof a);
 74       if(line[0] == 'P')
 75       {
 76         for(int i = 0,k = 2;i<3;i++)
 77         {
 78           while(line[k]!=' '&&line[k]!='\0')
 79           {
 80             a[i] = a[i]*10+(line[k]-'0');
 81             k++;
 82           }
 83          // printf("a[%d]:%d\t",i,a[i]);
 84           k++;
 85         }
 86         //printf("P %d %d %d\n\n",a[0],a[1],a[2]);
 87         addedge(a[0],a[1],a[2]);
 88         addedge(a[1],a[0],-a[2]);
 89       }
 90       else
 91       {
 92         for(int i = 0,k = 2;i<2;i++)
 93         {
 94           while(line[k]!=' '&&line[k]!='\0')
 95           {
 96             a[i] = a[i]*10+(line[k]-'0');
 97             k++;
 98           }
 99           k++;
100         }
101         //printf("V %d %d\n",a[0],a[1]);
102         addedge(a[1],a[0],-1);
103       }
104     }
105 
106     for(int i = 1;i<=n;i++)
107     {
108       addedge(0,i,0);
109     }
110     if(spfa(0,n+1))printf("Reliable\n");
111     else printf("Unreliable\n");
112   }
113   return 0;
114 }
POJ2983

 

 

posted on 2013-12-18 16:58  Boapath  阅读(508)  评论(0编辑  收藏  举报