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

    前言

    差分约束系统是一种特殊的N元一次不等式组,不等式组的每一个不等式称为一个约束条件。通过不等式的变形,可以通过最短路算法对差分约束系统进行求解。


    相关定义

    • 约束条件:一个满足的不等式称为一个约束条件

    • 差分约束系统:一个由若干个约束条件构成的N元一次不等式组称为一个差分约束系统


    性质

    • $i,jin[1,n]$,$kin [1,m]$

    • $n_kin R$


    观察约束条件的通式$a_i-a_jleq n_k$,移项得$a_ileq a_j+n_k$,是不是和最短路转移中的三角形不等式$d_yleq d_x+edge_i$很像?(明明是一模一样)。因此,我们可以将每个约束条件$a_i-a_jleq n_k$看作一条从$j$连向$i$的权值为$n_k$的有向边,利用最短路算法进行求解。由于$n_k$可能为负数,因此要用SPFA进行求解(当然,dijkstra算法经过一番乱搞也是可以实现的)。

    如果题目给出的约束条件是形如$a_i-a_jgeq n_k$,要怎么办呢?有两种办法,一是原样插入,跑最长路;二是将其变形为$a_j-a_ileq -n_k$,将它看作一条从$i$连向$j$的权值为$-n_k$的有向边,还是跑最短路。

    接下来会对差分约束系统的实现进行系统地讲解。在判断差分约束系统的可行性时需要判断负环,因此接下来会先讲解判定负环的方法。


    负环

    定义

    • 环:若一张有向图(若为无向图,则将一条无向边看作两条反向的有向边)上存在一条可以从一个节点经过它回到该节点,则这条路径称作环

    • 负环:边的权值和为负数的环称为负环

    观察最短路转移的三角形不等式$d_yleq d_x+edge_i$,若图中存在负环,则$d_y$会随着更新越来越小且会不停更新,即最短路算法Bellman-Ford和SPFA永远不能正常结束。因此,Bellman-Ford算法和SPFA算法可以用来判定负环的存在。

    Bellman-Ford算法

    Bellman-Ford算法判定负环理论复杂度较慢(虽然SPFA也可能被卡到和Bellman-Ford一样慢),因此不对此进行具体讲解。大致地说,Bellman-Ford算法最多会进行$n-1$轮迭代,若迭代轮数超过$n-1$轮,说明算法没有正常运行,即图中存在负环。时间复杂度$O(nm)$。

    SPFA算法

    显然一条最短路最多经过$n$个节点,若经过超过$n$个节点,则说明算法没有正常运行,图中存在负环。可以在更新最短路的同时维护一个$cnt$数组,初始化$cnt_s=0$,其中$s$表示最短路的起点,在更新$d_y=d_x+edge_i$的同时,更新$cnt_y=cnt_x+1$。若算法过程中出现$cnt_igeq n$,说明图中存在负环。理论上时间复杂度会比Bellman-Ford算法稍快一点,但仍可能被卡到$O(nm)$。

    众所周知,SPFA早在NOI2018就被宣布死亡了。因此,很可能出现算法超时的情况。SPFA求负环有一些玄学优化,如把队列换成栈、把bfs改成dfs等,这些玄学优化的确可以提高SPFA判负环的效率,但据说有可能严重降低不存在负环时求最短路的时间。当然,宁可用一些玄学的方法,也不能任它TLE直接宣布死亡。因此还有一个玄学的方法,就是当你知道程序要超时的时候,救程序于水火之中,直接判断它存在负环输出结束。换句话说,你可以限制程序运行的时间,或者限制队列的长度,让它不超时,直接判定存在负环。虽然这样可能会导致答案错误,但总比直接TLE宣布死亡来得好。

    众所周知,ctime库里有一个clock()函数可以返回运行的时间,利用它,若程序即将超过时限,直接判定为负环即可。用法如下:

    t=clock()/CLOCKS_PRE_SEC;//CLOCKS_PRE_SEC是一个常数

    这样t就是当前程序的已运行时间。注意,需要在程序开头先存储一下从启动程序到运行开始使用的时间,然后用t减去这个时间,才是运行的正确时间。

    //洛谷P3385 【模板】负环
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int N=2e3+100,M=3e3+100;
    int t,n,m,tot;
    int head[N],ver[2*M],edge[2*M],Next[2*M];
    int d[N],cnt[N];
    bool v[N];
    void add(int x,int y,int z)
    {
        ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
    }//邻接表存边
    bool spfa()
    {
        memset(v,0,sizeof(v));
        memset(d,0x3f,sizeof(d));
        memset(cnt,0,sizeof(cnt));//初始化
        queue<int> q;
        q.push(1);
        d[1]=0,v[1]=1;
        while(q.size())
        {
            int x=q.front();q.pop();v[x]=0;
            for(int i=head[x];i;i=Next[i])
            {
                int y=ver[i];
                if(d[x]+edge[i]<d[y])
                {
                    d[y]=d[x]+edge[i];
                    cnt[y]=cnt[x]+1;
                    if(cnt[y]>=n)
                        return 1;//判定负环
                    if(!v[y])
                    {
                        q.push(y);
                        v[y]=1;
                    }
                }
            }
        }
        return 0;//不存在负环
    }//spfa
    int main()
    {
        scanf("%d",&t);
        while(t--)
        {
            memset(head,0,sizeof(head));
            memset(ver,0,sizeof(ver));
            memset(edge,0,sizeof(edge));
            memset(Next,0,sizeof(Next));
            tot=0;//初始化
            scanf("%d%d",&n,&m);
            for(int i=1;i<=m;i++)
            {
                int x,y,z;
                scanf("%d%d%d",&x,&y,&z);
                add(x,y,z);
                if(z>=0)
                    add(y,x,z);
            }
            if(spfa())
                puts("YE5");
            else
                puts("N0");
        }
        return 0;
    }

    差分约束系统

    开头已经讲过最短路求解差分约束系统的大致方法。显然,对于差分约束系统的一组解{$a_i|iin[1,n]$},{$a_i+Delta|iin[1,n]$}也是一组解(因为$Delta$会在作差是被消掉)。因此,为了判断差分约束系统是否有解,我们可以先求一组负数解,即增加一个编号为0的节点(这个节点的编号应该是一个对题目没有影响的编号,若有的题目用编号0会对答案产生影响,则需要另取编号),令$a_0=0$,并从0号节点向每个节点连一条边,这样就可以保证$forall i,a_ileq0$。

    以0号节点为起点跑最短路,显然,若图中存在负环,则说明永远满足不了所有的约束条件,差分约束系统无解;否则{$a_i|iin[1,n]$}就是一组解。特别地,若跑的是最长路,则求的是一组正解,无解的判定条件为图中存在正环。

    //洛谷P1993 小K的农场
    //卡时
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<ctime>
    using namespace std;
    const int INF=1.99,N=1e4+100,M=2e4+100;
    int n,m,tot,start;
    int head[N],ver[2*M],Next[2*M],edge[2*M];
    int d[N],cnt[N];
    bool v[N];
    void add(int x,int y,int z)
    {
        ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
    }//邻接表存边
    bool spfa()
    {
        memset(d,0x3f,sizeof(d));
        memset(v,0,sizeof(v));
        queue<int> q;
        q.push(0);
        d[0]=0,v[0]=1;
        while(q.size())
        {
            if((double)(clock()-start)/CLOCKS_PER_SEC>=INF)
                return 1;//要超时直接判定存在负环
            int x=q.front();q.pop();v[x]=0;
            for(int i=head[x];i;i=Next[i])
            {
                int y=ver[i];
                if(d[x]+edge[i]<d[y])
                {
                    d[y]=d[x]+edge[i];
                    cnt[y]=cnt[x]+1;
                    if(cnt[y]>n)
                        return 1;
                    if(!v[y])
                    {
                        v[y]=1;
                        q.push(y);
                    }
                }
            }
        }
        return 0;
    }//spfa
    int main()
    {
        start=clock();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            add(0,i,0);
        for(int i=1;i<=m;i++)
        {
            int k,x,y,z;
            scanf("%d",&k);
            if(k==1)
            {
                scanf("%d%d%d",&x,&y,&z);
                add(x,y,-z);
            }
            if(k==2)
            {
                scanf("%d%d%d",&x,&y,&z);
                add(y,x,z);
            }
            if(k==3)
            {
                scanf("%d%d",&x,&y);
                add(x,y,0),add(y,x,0);
            }
        }
        if(spfa())
            puts("No");
        else
            puts("Yes");
        return 0;
    }

    习题

    负环
    差分约束系统

    2019.9.9 于厦门外国语学校石狮分校

  • 相关阅读:
    【基础算法】- 全排列
    【基础算法】- 2分查找
    区块链培训
    Static Binding (Early Binding) vs Dynamic Binding (Late Binding)
    test
    No data is deployed on the contract address!
    "throw" is deprecated in favour of "revert()", "require()" and "assert()".
    Variable is declared as a storage pointer. Use an explicit "storage" keyword to silence this warning.
    京都行
    Failed to write genesis block: database already contains an incompatible
  • 原文地址:https://www.cnblogs.com/TEoS/p/11490547.html
Copyright © 2011-2022 走看看