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

    差分约束的定义:

      差分约束系统是一种特殊的一元一次不等式组,约束条件就是一些以两个变量做差的形式构成,形如Xi-Xj<=Ck(Ck为常数,是一个已知的量),

       我们所需要求一组解,使得所有的约束条件的不等式都得到满足。

       将差分约束的条件进行移项变形可以得到:Xi<=X+ Ck,仔细观察可以发现这个式子与求单源最短路中的不等式 dis[v]<=dis[u]+p[i].val非常相似,

       因此可以把每个变量Xi 看成图中的一个结点i,对于每个约束条件Xi-Xj<=Ck,由结点j向结点i连一条权值为Ck的有向边,

       至于为什么是由结点j连向结点i而不是结点i连向结点j的原因是在必须要满足Xi<=X+ Ck,而在跑最短路的时候只有在dis[v]>=dis[u]+p[i].val的时候才会更新dis[v]=dis[u]+p[i].val,这样才满足了dis[v]<=dis{u]+p[i].val的条件,且最短路中是u向v连有向边,所以这里也是j向i连有向边。

       在平常的题目有些约束条件并不是这样的,但我们可以通过适量的变形来达到这样的效果,比如说条件为:Xi-Xj >=Ck

       此时我们仍然看成j向i连长为Ck的有向边,只是由跑单源最短路改成跑单源最长路而已,或者将不等式两边同是乘以-1并移项,将式子变为Xj-Xi<= -Ck后,再跑最短路。

     

    习题:

    L1250 种树:https://www.luogu.org/problemnew/show/P1250

    解题思路:

      差分约束的板子题。。。

      设dis[i]表示[1,i]之间一共种了dis[i]颗树,所以有:dis[y]-dis[x-1]>=z,dis[y]>=dis[x-1]+z,由x-1向y建一条权值为z的有向边,转化为跑最长路。

    但是题目中也有隐含条件:每个部分最多能种1棵树,所以有:

    ①:dis[i]-dis[i-1]<=1 dis[i-1]-dis[i]>=-1

    ②:dis[i]-dis[i-1]>=0

    成功建完图后跑一遍最长路就行啦。

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 30009
    struct edge
    {
        int to,nxt,val;
    }p[maxn*3];
    int head[maxn],dis[maxn];
    queue<int>q;
    int n,m,k,ans,tot,cnt;
    bool vis[maxn];
    
    void add(int x,int y,int z)
    {
        ++cnt;
        p[cnt].to=y;
        p[cnt].nxt=head[x];
        p[cnt].val=z;
        head[x]=cnt;
     } 
    void Spfa()
    {
        memset(dis,-1,sizeof(dis));
        dis[0]=0;
        vis[0]=1;
        q.push(0);
        while(q.size())
        {
            int u=q.front();
            q.pop();
            vis[u]=0;
            for(int i=head[u];i;i=p[i].nxt)
            {
                int v=p[i].to;
                if(dis[v]<dis[u]+p[i].val)
                {
                    dis[v]=dis[u]+p[i].val;
                    if(!vis[v])
                    {
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            add(i,i+1,0);
        for(int i=1;i<=n;i++)
            add(i,i-1,-1);
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x-1,y,z);
        }
        Spfa();
        printf("%d
    ",dis[n]);
        return 0;
    }
    View Code

     #10087. 「一本通 3.4 例 1」Intervals:https://loj.ac/problem/10087

    解题思路:

      和上一题思路一样。

      设dis[i]表示从[0,i]内最少选出dis[i]个数,由题意可得:dis[b]-dis[a-1]>=C,由此可以将其转换为差分约束的模型。

      但是题目中仍然有有一些较隐含的条件:

      ①:dis[i]-dis[i-1]>=0 ,0到i之间选出的数肯定要比0到i-1之间选出的数多

      ②:dis[i]-dis[i-1]<=1 ,由于每个数至多只能选一次,所以差值一定要小于等于1,将其变形为dis[i-1]-dis[i]>=-1。

      最后把所有整数作为图中结点并适当连边跑单源最短路就行了。

     

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 50009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    struct edge
    {
        int to,nxt,val;
    }p[maxn];
    bool vis[maxn];
    int dis[maxn],head[maxn];
    int n,m,k,ans,tot,cnt,sum,mx,mn=INF; 
    void add(int x,int y,int z)
    {
        ++cnt,p[cnt].to=y,p[cnt].nxt=head[x],p[cnt].val=z,head[x]=cnt;
    }
    void Spfa(int s)
    {
        for(int i=mn;i<=mx;i++)
            dis[i]=-INF;
        dis[s]=0,vis[s]=1;
        queue<int>q;
        q.push(s);
        while(q.size())
        {
            int u=q.front();q.pop();
        //    cout<<"nice "<<u<<endl;
            for(int i=head[u];i;i=p[i].nxt)
            {
                int v=p[i].to;
                if(dis[v]<dis[u]+p[i].val)
                {
                    dis[v]=dis[u]+p[i].val;
                    if(!vis[v])
                    {
                        vis[v]=1;
                        q.push(v);
                    }
                }
             } 
             vis[u]=0;
        }
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read();
        for(int i=1;i<=n;i++)//由于0也是一个结点,但连边的时候需要用到i-1,当i==0时下标出现负数,所以直接将没个整数加1,避免负数下标 
        {
            int x=read(),y=read(),k=read();++x,++y;
            mx=max(mx,y),mn=min(mn,x-1);
            add(x-1,y,k);
        }
    //    cout<<"ok "<<mn<<" "<<mx<<endl;
        for(int i=mn;i<=mx;i++)//隐含的条件,可以保证整张图连通。 
            add(i,i+1,0),add(i+1,i,-1);
        Spfa(mn);
    //    for(int i=mn;i<=mx;i++)
    //        cout<<dis[i]<<" ";
    //    cout<<endl; 
        printf("%d
    ",dis[mx]);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

    L2294 [HNOI2005]狡猾的商人:https://www.luogu.org/problemnew/show/P2294

    解题思路:

      列出不等式:z<=dis[y]-dis[x-1]<=z,将其拆分为两个不等式,分别建边。

    ①:dis[y]-dis[x-1]<=z  dis[y]<=dis[x-1]+z 

    ②:dis[y]-dis[x-1]>=z  dis[x-1]<=dis[y]-z

    最后跑最短路,判断是否有负环的存在。

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 109
    #define maxm 1009
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    bool vis[maxn];
    int head[maxn],dis[maxn];
    struct edge
    {
        int to,nxt,val;
    }p[maxm<<2];
    int n,m,k,ans,tot,cnt,T; 
    
    void add(int x,int y,int z)
    {
        p[++cnt]={y,head[x],z},head[x]=cnt;
    }
    bool Spfa(int u)
    {
        vis[u]=1;
        for(int i=head[u];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(dis[v]>dis[u]+p[i].val)
            {
                dis[v]=dis[u]+p[i].val;
                if(vis[v]||!Spfa(v))
                    return false;
            }
        }    
        vis[u]=0;
        return true;
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        T=read();
        while(T--)
        {
            memset(dis,INF,sizeof(dis));
            memset(vis,0,sizeof(vis));
            memset(head,0,sizeof(head)),cnt=0;
            n=read(),m=read();
            for(int i=1;i<=m;i++)
            {
                int x=read(),y=read(),z=read();
                add(y,x-1,-z),add(x-1,y,z);
            }
            dis[0]=0;
            if(!Spfa(0))
                puts("false");
            else
                puts("true");
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

     

    L1993 小K的农场:https://www.luogu.org/problemnew/show/P1993

    解题思路:

      做多了差分约束的题目就会发现都是套路题。。。

      列出不等式:

    ①:dis[a]-dis[b]>=c

    ②:dis[a]-dis[b]<=c

    ③:0<=dis[a]-dis[b]<=0

     把以上的不等式全部转化成>=的形式,最后跑最长路判断是否有正环的存在。

    还要建结点0和任意一个点的边,此时的0相当于一个超级源,为了保证图连通。

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 10009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    bool vis[maxn];
    int head[maxn],dis[maxn];
    int n,m,k,ans,tot,cnt;
    bool flag=false;
    struct edge
    {
        int to,nxt,val;
    }p[maxn<<2];
    
    void add(int x,int y,int z)
    {
        p[++cnt]={y,head[x],z},head[x]=cnt;
    }
    
    bool Spfa(int u)
    {
        vis[u]=1;
        for(int i=head[u];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(dis[v]<dis[u]+p[i].val)
            {
                dis[v]=dis[u]+p[i].val;
                if(vis[v])
                    return false;
                if(!Spfa(v))
                    return false;
            }
        }
        vis[u]=0;
        return true;
    }
    
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int opt=read(),x=read(),y=read(),z;
            if(opt==3)
            {
                add(x,y,0),add(y,x,0);
                continue;
            }
            z=read();
            if(opt==1)
                add(y,x,z);
            else
                add(x,y,-z);
        }
        for(int i=1;i<=n;i++)
            add(0,i,0),dis[i]=-INF;
        if(Spfa(0))
            puts("Yes");
        else
            puts("No");
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

    L1260 工程规划:https://www.luogu.org/problemnew/show/P1260

    解题思路:

      差分约束。。。列不等式。。。

    dis[x]-dis[y]<=z dis[x]<=dis[y]+z 由y向x连一条权值为z的有向边。。。

    跑最短路,对于每一个没有做过的点都要做一次,最后对dis数组取min,再拿dis数组减去min就是ans了。

    这样做可以保证至少有一个为0,而且为0的可能不止一个。

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 1009
    #define maxm 5009
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    
    bool flag=true;
    int head[maxn],dis[maxn],cont[maxn];
    int n,m,k,ans,tot,cnt,mn=INF;
    struct edge
    {
        int to,nxt,val;
    }p[maxm]; 
    
    void add(int x,int y,int z)
    {
        p[++cnt]={y,head[x],z},head[x]=cnt;
    }
    
    void Spfa(int s)
    {
        dis[s]=0,cont[s]=1;
        queue<int>q;q.push(s);
        while(q.size())
        {
            int u=q.front();q.pop();
            for(int i=head[u];i;i=p[i].nxt)
            {
                int v=p[i].to;
                if(dis[v]>dis[u]+p[i].val)
                {
                    dis[v]=dis[u]+p[i].val;
                    cont[v]++;
                    if(cont[v]>n)
                    {
                        puts("NO SOLUTION");
                        exit(0);
                    }
                    q.push(v);
                }
            }
        }
    }
    
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        memset(dis,INF,sizeof(dis));
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read(),z=read();
            add(y,x,z);
        }
        for(int i=1;i<=n;i++)
            if(!cont[i])
                Spfa(i);
        for(int i=1;i<=n;i++)
            mn=min(mn,dis[i]);
        for(int i=1;i<=n;i++)
            printf("%d
    ",dis[i]-mn);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

    L3275 [SCOI2011]糖果:https://www.luogu.org/problemnew/show/P3275

    解题思路:

      还是差分约束。。。

      还是列不等式。。。

       还是跑最长路。。。

      注意事项:输入有A=B的情况,如果矛盾直接退出就好了,最后和0建边的时候要倒序枚举,因为数据的特殊性,题解还是讨论里好像有说吧,就是一条长链的情况。。。

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 100009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    bool flag=false;
    int head[maxn],cont[maxn];
    ll dis[maxn];
    struct edge 
    {
        int to,nxt;
        ll val;
    }p[maxn*10];
    int n,m,k,tot,cnt;
    ll ans; 
    
    void add(int x,int y,ll z)
    {
        p[++cnt]={y,head[x],z},head[x]=cnt;
    }
    
    bool Spfa(int s)
    {
        dis[s]=0,cont[s]=1;
        queue<int>q;q.push(s);
        while(q.size())
        {
            int u=q.front();q.pop();
            for(int i=head[u];i;i=p[i].nxt)
            {
                int v=p[i].to;
                if(dis[v]<dis[u]+p[i].val)
                {
                    dis[v]=dis[u]+p[i].val;
                    cont[v]++;
                    if(cont[v]>=n)
                        return 0;
                    q.push(v);
                }
            }
        }
        return 1;
    }
    
    
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int opt=read(),a=read(),b=read();
            if(opt==1)
                add(a,b,0),add(b,a,0);
            if(opt==2)
            {
                if(a==b)
                {
                    puts("-1");
                    return 0;    
                }
                add(a,b,1);
            }
                
            if(opt==3)
                add(b,a,0);
            if(opt==4)
            {
                if(a==b)
                {
                    puts("-1");
                    return 0;
                }
                add(b,a,1);
            }
            if(opt==5)
                add(a,b,0);
        }
    //    for(int i=1;i<=n;i++)
    //        add(0,i,1);
        for(int i=n;i>=1;i--)
            add(0,i,1);
        if(!Spfa(0))
            puts("-1");
        else
        {
            ans=0;
            for(int i=1;i<=n;i++)
                ans+=dis[i];
            printf("%lld
    ",ans);
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

    L4878 [USACO05DEC]layout布局:https://www.luogu.org/problemnew/show/P4878

    解题思路:

      列不等式(啊,我快疯了):

    ①:dis[y]-dis[x]<=z  dis[y]<=dis[x]+z

    ②:dis[y]-dis[x]>=z dis[x]<=dis[y]-z

    跑最短路。。。

      注意事项:这题特意卡了后3个测试点,要先从0跑一遍判断图的连通性,再从1跑一遍。

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 1009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    bool vis[maxn];
    int head[maxn],dis[maxn];
    struct edge
    {
        int to,nxt,val;
    }p[maxn*10*2];
    int n,m,k,ans,tot,cnt;
    
    void add(int x,int y,int z)
    {
        p[++cnt]={y,head[x],z},head[x]=cnt;
    }
    
    bool Spfa(int u)
    {
        vis[u]=1;
        for(int i=head[u];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(dis[v]>dis[u]+p[i].val)
            {
                dis[v]=dis[u]+p[i].val;
                if(vis[v]||!Spfa(v))
                    return false;
            }
        }
        vis[u]=0;
        return true;
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
    
        n=read(),m=read(),k=read();
        for(int i=1;i<=n;i++)
            add(0,i,0);
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read(),z=read();
            add(x,y,z);
        }
        for(int i=1;i<=k;i++)
        {
            int x=read(),y=read(),z=read();
            add(y,x,-z);
        }    
        memset(dis,INF,sizeof(dis));
        dis[0]=0;
        if(!Spfa(0))
        {
            puts("-1");
            return 0;
        }
        memset(dis,INF,sizeof(dis));
        dis[1]=0;
        Spfa(1);
        if(dis[n]==INF)
            puts("-2");
        else
            printf("%d
    ",dis[n]);    
    /*    for(int i=1;i<=n;i++)
            cout<<dis[i]<<" ";
        cout<<endl;*/
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code
  • 相关阅读:
    Adobe Flash Player因过期而遭到阻止”的内幕起因和解决办法
    SQL中isnull,nullif,coalesce的用法
    sql server全局变量
    关于分组后字段拼接的问题
    查询某个分组中多行字段值的拼接字串的方法--access
    分组统计(平均值计算)
    excel,sql server,access数据之间相互导入导出
    数据查询和管理
    我为什么学习Windows编程
    zStack学习笔记(原创,绝对不是抄的……)
  • 原文地址:https://www.cnblogs.com/Dxy0310/p/9780840.html
Copyright © 2011-2022 走看看