前言
差分约束系统是一种特殊的N元一次不等式组,不等式组的每一个不等式称为一个约束条件。通过不等式的变形,可以通过最短路算法对差分约束系统进行求解。
相关定义
-
约束条件:一个满足的不等式称为一个约束条件
-
差分约束系统:一个由若干个约束条件构成的N元一次不等式组称为一个差分约束系统
性质
-
$i,j\in[1,n]$,$k\in [1,m]$
观察约束条件的通式$a_i-a_j\leq n_k$,移项得$a_i\leq a_j+n_k$,是不是和最短路转移中的三角形不等式$d_y\leq d_x+edeg_i$很像?(明明是一模一样)。因此,我们可以将每个约束条件$a_i-a_j\leq n_k$看作一条从$j$连向$i$的权值为$n_k$的有向边,利用最短路算法进行求解。由于$n_k$可能为负数,因此要用SPFA进行求解(当然,dijkstra算法经过一番乱搞也是可以实现的)。
如果题目给出的约束条件是形如$a_i-a_j\geq n_k$,要怎么办呢?有两种办法,一是原样插入,跑最长路;二是将其变形为$a_j-a_i\leq -n_k$,将它看作一条从$i$连向$j$的权值为$-n_k$的有向边,还是跑最短路。
接下来会对差分约束系统的实现进行系统地讲解。在判断差分约束系统的可行性时需要判断负环,因此接下来会先讲解判定负环的方法。
负环
定义
-
环:若一张有向图(若为无向图,则将一条无向边看作两条反向的有向边)上存在一条可以从一个节点经过它回到该节点,则这条路径称作环
-
负环:边的权值和为负数的环称为负环
观察最短路转移的三角形不等式$d_y\leq 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_i\geq 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|i\in[1,n]$},{$a_i+\Delta|i\in[1,n]$}也是一组解(因为$\Delta$会在作差是被消掉)。因此,为了判断差分约束系统是否有解,我们可以先求一组负数解,即增加一个编号为0的节点(这个节点的编号应该是一个对题目没有影响的编号,若有的题目用编号0会对答案产生影响,则需要另取编号),令$a_0=0$,并从0号节点向每个节点连一条边,这样就可以保证$\forall i,a_i\leq0$。
以0号节点为起点跑最短路,显然,若图中存在负环,则说明永远满足不了所有的约束条件,差分约束系统无解;否则{$a_i|i\in[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; }
习题
负环
-
模板题:【模板】负环
差分约束系统
来源:https://www.cnblogs.com/TEoS/p/11490547.html