差分约束系统

 

一、算法简介

  差分约束系统是一种特殊的N元一次不等式组,它包含N个变量,X1~Xn以及m个约束条件,每一个约束条件都是由两个变量作差构成的,形如Xi-Xj≤Ck,其中Ck是常数,1≤i,j≤N,1≤k≤M,解决的问题是求一组解,X1=a1,X2=a2~~~Xn=an,使得所有的约束条件都得到满足。

  差分约束系统的每一个约束条件Xi-Xj≤Ck可以变形为最短路中的三角不等式,Xi≤Xj+Ck,因此,可以把每一个变量Xi看作有向图中的一个结点i,对于每一个约束条件Xi-Xj≤Ck,为从结点j想结点i连一条长度为Ck的有向边

二、例题

  UVA1723 Intervals

  此题是差分约束的裸题,但我之前也不知道差分约束系统是什么。

  定义:差分约束系统是一种特殊的N元一次不等式,包含N个变量及M个约束条件,每个约束条件由两个变量作差构成,形如Xi-Xj>=Ck,Ck是常数,1<=i,j<=N,1<=k<=M。如

  X1-X0>=2
  X2-X1>=3
  X3-X2>=2
  X3-X0>=6

  通过消元可得到X3-X0的最小值为7。假若我们从0->1建一条权值为2的边,从1->2建一条权值为3的边,以此类推。从0到3跑一遍SPFA求最长路,发现也为7。

  这不是个巧合,因为求最长路时,SPFA的松弛操作后,dis[v]>=dis[u]+g[i].w,与不等式Xi>=Xj+Ck性质一样,所以SPFA可以求出最小值,又因为不等式同大取大,所以要求出最长路。所以SPFA死了吗?

  最后,一定要注意输出,每个答案空一行,最后一个答案最后要输出换行。

  本题就是从0-50000选出最少的数,使每个区间至少有c个数被选,这是求最小值,所以我们待会要将所有的不等式转换成">="的形式 我们用数组d[i]表示0-i之间至少要选多少个数,所以对于每一个约束条件,可以看成d[b_i]-d[a_i-1]>=c_id[bi]d[ai1]>=ci

  d[bi]>=ci+d[ai1]

  因此我们在a[i-1]和b[i]之间建一条长度为c[i]的边

  上述建边过程只是题干中给出的条件,我们还要找出题目中的隐藏条件(隐藏条件视题目而为,有则加)。

  因为d[i]表示的是0-i之间要选的最小个数,所以d[i]一定要大于d[i-1];又因为每个数只能选一次,所以d[i]-d[i-1]要么为0,要么为1;

  由上述两个条件,我们对应出两个不等式d_i-d_{i-1}>=0didi1>=0d_{i-1}-d_{i}>=-1di1di>=1

  所以我们在i-1和i之间连长度为0的有向边,在i和i-1之间连长度为-1的有向边

  因为i的范围是0-50000所以i-1会变成负数,我们都知道,若数组的下标为负,程序会运行错误,所以我们不妨给所有的变量加1,这样就不会出现负下标了,所以上述加边过程可简述为

    1---->在a[i]和b[i]+1之间连一条长度为c[i]的有向边

    2---->在i和i+1之间连一条长度为0的有向边

    3---->在i+1和i之间连一条长度为-1的有向边

  接着,设d[0]为1,以0为起点求单元最长路

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
int t,n,head[500005],tot,ma,dis[500005];
bool vis[500005];
struct node{
    int u,v,next,w;
}g[5000005];
inline int rea(){//快读
    int s=0,g=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            g=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+(ch^48);
        ch=getchar();
    }
    return s*g;
}
inline void add(int u,int v,int w){//建图
    g[++tot].u=u;
    g[tot].v=v;
    g[tot].w=w;
    g[tot].next=head[u];
    head[u]=tot;
}
inline void spfa(){//SPFA求最长路,即可得出最小值
    queue<int>q;
    q.push(0);
    vis[0]=true;
    dis[0]=0;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i;i=g[i].next){
            int v=g[i].v;
            if(dis[v]<dis[u]+g[i].w){
                dis[v]=dis[u]+g[i].w;
                if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
}
int main(){
    t=rea();
    while(t--){
        memset(head,0,sizeof(head));
        memset(dis,-0x3f,sizeof(dis));
        tot=0;
        n=rea();
        for(int i=1;i<=n;i=-~i){
            int a=rea(),b=rea(),c=rea();
            add(a,b+1,c);//因为前缀和中,Sb-S(a-1)>=c,然而a-1要大于等于0,a有可能是0,所以我们加一
            ma=max(ma,b+1);
        }
        for(int i=1;i<=ma;i=-~i){
            add(i-1,i,0);
            add(i,i-1,-1);//Si-S(i-1)>=0,S(i-1)-Si>=-1
        }
        spfa();
        printf("%d\n",dis[ma]);
        if(t)
            printf("\n");//注意输出!!!
    }
    return 0;
}

 

 

三、相关文章链接

  https://blog.csdn.net/dragon60066/article/details/80245797

 

 

 

posted @ 2019-07-26 10:35  SeanOcean  阅读(1651)  评论(0编辑  收藏  举报