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 于厦门外国语学校石狮分校

  • 相关阅读:
    低版本ie模式 360兼容模式的兼容性调节以及控制代码
    360浏览器兼容模式样式乱码的原因及解决办法
    iOS开发之UILabel
    代码大全--第六章--可以工作的类
    读书笔记--软件项目成功之道
    extern "C"的用法解析(转)
    基于Ubuntu 15.04 LTS编译Android5.1.0源代码 (转)
    Global.asax 文件是什么(转)
    设备扩展(DEVICE_EXTENSION)
    IRP_MJ_CREATE
  • 原文地址:https://www.cnblogs.com/TEoS/p/11490547.html
Copyright © 2011-2022 走看看