zoukankan      html  css  js  c++  java
  • 差分约束

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

     

      对于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
    为什么这样是对的? 为什么那样是错的? 凭什么这样是最优的? 凭什么那样不是最优的? 为什么没有更优的解法? 就不能找一个更好的解法吗? 若不知其所以然何苦知其然?
  • 相关阅读:
    Spring Security -- 添加图形验证码(转载)
    Spring Security -- 自定义用户认证(转载)
    pandas agg 后降低df表层
    判断是否有人跟踪车辆的方案
    格隆汇笔记-黄勇演讲
    Mac mysql 忘记root密码的解决方法
    sql 同步2个表中的一个字段数据
    Linux下配置用msmtp和mutt发邮件
    spark StructType的应用,用在处理mongoDB keyvalue
    Idea2018旗舰版破解方法
  • 原文地址:https://www.cnblogs.com/PeanutPrince/p/3480675.html
Copyright © 2011-2022 走看看