首先提一句,这系统是基于最短路的基础上的。(尤其是SPFA)
单源最短路径问题
单源最短路径=Single Source Shortest Path
即在有向图(或无向图)中求解给定点到其他点之间的最短距离
我们已知的方法是…… Dijkstra算法
蛋蛋蛋但是Dijkstra算法的有局限性!!!
如果边权为负值,Dijkstra算法还正确吗?
求解下图A至其他点的最短距离(单源最短路径)
算法步骤:
- 标记点A
- Dist[C]=2最小,标记点C
- Dist[B]=3最小,标记点B
结束 但是ShortestDist[C]=1
错误结果的原因
Dijkstra的缺陷就在于它不能处理负权回路:
Dijkstra对于标记过的点就不再进行更新了,所以即使有负权导致最短距离的改变也不会重新计算已经计算过的结果
我们需要新的算法——Bellman-Ford
关于Bellman-Ford我就不细讲了,详情看 最短路
Bellman-Ford 进一步的优化——SPFA算法
时间复杂度一般认为是O(kE) 其中k是一个较大的常数,
不好估计,但是可以看出SPFA算法效率应当是很高的
经验表明Dijkstra算法的堆优化要比SPFA快(能用Dijkstra还是别用SPFA,有些题卡常,卡SPFA)
但SPFA比普通的Dijkstra算法快。
而SPFA算法可以处理负权的问题,而且比Dijkstra算法的堆优化的代码要容易实现,因此SPFA是一个很好的算法。
差分约束系统(终于步入正题了)
啥是差分约束?
摘自百科
例如:求x3-x0的最大值
x1-x0<=2 (1)
x2-x0<=7 (2)
x3-x0<=8 (3)
x2-x1<=3 (4)
x3-x2<=2 (5)
当然通过手动推导可以求出答案x3-x0<=7
通过叠加不等式可以推导出x3-x0<=7,最大值即为7,我们可以通过建立一个图,包含4个顶点,对每个xj-xi<=bk,建立一条i到j的有向边,权值为bk。
通过求出这个图的x0到x3的最短路可以知道也为7,这是巧合吗?
并不是 之所以差分约束系统可以通过图论的最短路来解,是因为xj-xi<=bk,
会发现它类似最短路中的三角不等式d[v] <=d[u]+w[u,v],即d[v]-d[u]<=w[u,v]。
而求取最大值的过程类似于最短路算法中的松弛过程。
三角不等式:求C-A的最大值
B - A <= c (1)
C - B <= a (2)
C - A <= b (3)
如果要求C-A的最大值,可以知道max(C-A)= min(b,a+c),而这正对应了下图中C到A的最短路。
因此,对三角不等式加以推广,变量n个,不等式m个,要求xn-x1的最大值,便就是求取建图后的最短路。
同样地,如果要求取差分约束系统中xn-x1的最小值,便是求取建图后的最长路。
最长路可以通过spfa求出来,只需要改下松弛的方向即可,即if(d[v] < d[u] + dist(u,v)) d[v] = d[u] + dist(u,v)。当然我们可以把图中所有的边权取负,求取最短路,两者是等价的。
最后一点,建图后不一定存在最短路/最长路,因为可能存在无限减小/增大的负环/正环,题目一般会对应于不同的输出。判断差分约束系统是否存在解一般判环即可。
应用
- 求取最短路
- 求取最长路
- 判断差分约束系统的解是否存在
当然这三种也可能会相互结合。
解法
1、 根据条件把题意通过变量组表达出来得到不等式组,注意要发掘出隐含的不等式,比如说前后两个变量之间隐含的不等式关系。
2、 进行建图:
首先根据题目的要求进行不等式组的标准化。
(1)、如果要求取最小值,那么求出最长路,那么将不等式全部化成xi – xj >= k的形式,这样建立j->i的边,权值为k的边,如果不等式组中有xi – xj > k,因为一般题目都是对整形变量的约束,化为xi – xj >= k+1即可,如果xi – xj = k呢,那么可以变为如下两个:xi – xj >= k, xi – xj <= k,进一步变为xj – xi >= -k,建立两条边即可。
(2)、如果求取的是最大值,那么求取最短路,将不等式全部化成xi – xj <= k的形式, 这样建立j->i的边,权值为k的边,如果像上面的两种情况,那么同样地标准化就行了。
(3)、如果要判断差分约束系统是否存在解,一般都是判断环,选择求最短路或者最长路求解都行,只是不等式标准化时候不同,判环地话,用spfa即可,n个点中如果同一个点入队超过n次,那么即存在环。
值得注意的一点是:建立的图可能不联通,我们只需要加入一个超级源点,比如说求取最长路时图不联通的话,我们只需要加入一个点S,对其他的每个点建立一条权值为0的边图就联通了,然后从S点开始进行spfa判环。最短路类似。
3、 建好图之后直接spfa或bellman-ford求解,不能用dijstra算法,因为一般存在负边,注意初始化的问题。
代码
……………………没有…………
#include<cstdio> #include<iostream> #include<cmath> #include<string> #include<cstring> #include<stdlib.h> #include<queue> using namespace std; const int maxn=2000,maxm=1000000; const int inf=0x7f7f7f7f; int n,ml,md; struct edge{ int to,w,next; }e[maxm]; int head[maxn],cnt=1; int vis[maxn],dis[maxn],inq[maxn]; queue <int> q; int read() //快读 { int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } void add(int u,int v,int w) //前向星 { e[cnt].to=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt++; } void readdata() { n=read(),ml=read(),md=read(); for(int i=1;i<=ml;i++) //我讲的很详细了 { int a,b,c; a=read(),b=read(),c=read(); add(a,b,c); } for(int i=1;i<=md;i++) { int a,b,c; a=read(),b=read(),c=read(); add(b,a,-c); } for(int i=1;i<=n;i++) //超级原点,有时逆存比顺存块QAQ { add(0,i,0); } } void SPFA(int u)//SPFA,不会的看我博客https://www.cnblogs.com/mzyczly/p/11028962.html { memset(vis,0,sizeof(vis)); memset(dis,inf,sizeof(dis)); memset(inq,0,sizeof(inq)); q.push(u); vis[u]=1,dis[u]=0;vis[u]=1; while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(dis[x]+e[i].w<dis[y]) //最短路精髓 { dis[y]=dis[x]+e[i].w; inq[y]++; if(!vis[y]){ q.push(y); vis[y]=1; } if(inq[y]>=n) //判环 { printf("-1 "); exit(0); } } } vis[x]=0; } } int main() { readdata(); SPFA(0); SPFA(1); if(dis[n]==inf) //题目要求 printf("-2"); else printf("%d",dis[n]); return 0; }
再送几道题