最近开始学差分约束系统了,讲课时检验学习程度的一个好方法,于是我就在博客园试图讲一讲。
首先,我们要知道差分约束系统是什么:
如果一个不等式组由n个变量和m个约束条件形成,形成m个如a-b<=c(c是常数)的不等式,我们就称他为差分约束系统。
说简单点,差分约束系统就是求解一组符合不等式组的变量。
差分约束系统可以用图论算法中的最长路解。这次我们用SPFA。
我们知道了差分约束系统可以用最短路来解,那么就需要建图,建好图跑最短路就可以了,但问题是怎么建图呢?
要解决一个问题,可以举一些例子来帮助斯烤。我就来举一个a-b<=c的例子。
虽然这样子我们还是不知道怎么建图,但我们可以通过转化来把他转化成已知的知识,这样就可以知道如何建图。
我们可以把a-b<=c转化成a<=b+c,是不是,这样转化完,我们可以发现他和最短路的一个性质好像哦。就是zd(i)<=zd(j)+lj(i,j),嗯……用能看的懂的话翻译一下就是,一个点的最短路径,肯定小于等于另一个点的最短路径+过来的长度。届时,我们把a<=b+c代入一下,就可以知道如何建图了,那就是从b到a之间连接一个长度为c的路径。
那如果出现a-b>=c这种情况呢?不要慌,我们可以利用数学上学过的定律(具体那个我忘了),我们可以把>=两边的数都*-1,这个式子就会由a-b>=c变成b-a<=-c。然后我们就会做了。是不是很简单!
如果出现a-b=0这种情况直接都连就好了。
在跑之前,还要创造一个超级节点,来维持图的联通性。这样一来,整个图就建好了,我们只要跑一遍SPFA就可以得到想要的结果了。
好了讲完了。
哦哦还有代码
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; queue<long long>p; long long n,m,a,b,c,bj[5005],sz[5005],cs[5005],wz; long long ans,head[5005]; struct hehe { long long w,cd,syg; }lsqxx[10005];//边最多是10000条(应该) void add(long long t,long long w,long long cd)//神奇又好用的链式前向星 { ans++; lsqxx[ans].w=w;//结尾处 lsqxx[ans].cd=cd;//这条边的权值 lsqxx[ans].syg=head[t];//上一个起点是t的线的编号 head[t]=ans;//现在这个就是下一个的上一个(啊好乱) } bool qg() { for(int i=1;i<=n;i++) { sz[i]=1000;//求最短路当然先初始化 } while(p.empty()==false) { wz=p.front(); bj[wz]=0;//现在他出去了 p.pop(); for(int i=head[wz];i!=0;i=lsqxx[i].syg) { if(sz[lsqxx[i].w]>sz[wz]+lsqxx[i].cd)//松弛操作 { sz[lsqxx[i].w]=sz[wz]+lsqxx[i].cd; if(bj[lsqxx[i].w]==0)//如果已经在队列里面了,再加进去就是浪费时间了 { bj[lsqxx[i].w]=1; cs[lsqxx[i].w]++;//进队列的次数增加了 if(cs[lsqxx[i].w]==n)//在SPFA中,一个点进入队列n次就一定有负环 { return false; } p.push(lsqxx[i].w); } } } } return true; } int main() { cin>>n>>m; for(int i=1;i<=n;i++)//0号点是超级原点 { add(0,i,0); } for(int i=0;i<m;i++) { cin>>a>>b>>c;//输入条件 add(b,a,c);//根据条件建图 } p.push(0); if(qg()==false)//有负环,没有解的 { cout<<"NO"<<endl; }else { for(int i=1;i<=n;i++)//sz[i]是最短路,这是一种可行情况 { cout<<sz[i]<<" "; } cout<<endl; } return 0; }