【概述】
如果一个系统由 n 个变量 m 个约束条件组成,形成 m 个形如 的不等式,其中
,k 是常数,则称这 m 个不等式为差分约束系统(system of difference constraints),亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
如下图,就是一个差分约束系统
【求解方法】
差分约束系统的求解可以转化为图论的最短路来解决。
对于三角不等式,有:
- B - A <= c ①
- C - B <= a ②
- C - A <= b ③
若要求 C-A 的最大值,由 ①+②
易知:
- C-A<=c+a
- C-A<=b
由不等式基本原理可得:max(C-A)= min(b,a+c),即对应下图 C 到 A 的最短路。
因此对于最短路径:d(v) <= d(u) + w(u, v) ,移项得:d(v) - d(u) <= w(u, v),其形式与 x-y<=b 相同。
因此,对三角不等式加以推广,当有 n 个变量 m 个不等式,要求 Xn-X1 的最大值,就是求建图后的最短路,同样地,若要求取差分约束系统中 Xn-X1 的最小值,就是求建图后的最长路。
如果建图后对最短路的求取存在负环,则说明该差分约束系统无解。
【建图与具体实现】
求解差分约束系统的关键在于建图,建好图后使用 SPFA 算法直接判断有无负环即可判断该差分约束系统有无解。
具体方法:
1.新建一个图,n 个变量看作 n 个点,m 个约束条件作为 m 条边,每个顶点 Vi 分别对于一个未知量,每个有向边对应两个未知量的不等式。
1)对于 <= 的不等式,形如:dis[j]-dis[i]<=w,可化为 dis[j]<=dis[i]+w,建立从 i 到 j 权值为 w 的边
2)对于 >= 的不等式,形如:dis[j]-dis[i]>=w,可化为 dis[i]<=dis[j]-w,建立从 j 到 i 权值为 -w 的边
2.根据约束条件,进行初始化
1)若求差最大,则:dis[1]=0 且 dis[i]=INF
2)若求差最小,则:dis[1]=0 且 dis[i]=-INF
3.根据实际情况判断是否需要添加超级源点 Vs(0 号点)
1)若建成的图能保证连通,则直接求最短路即可,若建成的图不是连通图,为保证图的连通性,需要加一个源点 Vs,从 Vs 到任意点 Vi 的边权为 0,即:W(Vs,Vi)=0,然后从该点出发进行计算,那么最终求出源点 Vs 到其他所有点的最短距离就是差分约束系统的一个可行解,且可行解之间的差距最小。
原因:添加从虚点 Vs 到各个顶点 Vi 的权为 0 的边,是为了保证构造出的图是连通的,且由于虚点本身并不引入负环,因此设置虚点 Vs 后最短路仍然存在且每个约束仍满足。
2)若不添加超级源点,只是将初始距离设为 INF,且令其中一个点的初始距离为 0,然后就该点到其他所有点的最短距离,那么最短距离的集合就是一个可行解,且该可行解两两之间差距最大。
适用情况:保证问题一定存在解,即:不存在负环(否则从 1 号点到其他点没有路,但其他点的强连通分量中有负环)
4.根据结果进行解的判断
1)若源点 Vs 到某点 Xi 不存在最短路径,即:dis[Xi]=INF,则表示该点 Xi 表示的变量可取任意解,均能满足差分约束的要求
2)若存在源点 Vs 到某点 Xi 的最短路径,则得到该点 Xi 表示的变量的最大值
3)若求最短路的过程中出现负环,则该差分约束系统无解。
注:若所有的未知量都是正数,则不会存在负环,使用 Dijkstra 求解单源最短路即可;若存在负数,需使用 SPFA 来求解,判断有无负环
【模版】
1.Dijkstra 求解
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define INF 0x3f3f3f3f
#define N 1000001
struct Edge{
int from;
int to;
int w;
Edge(){}
Edge(int from,int to,int w):from(from),to(to),w(w){}
}edge[N];
int head[N],next[N],num;
int dis[N];
bool vis[N];
void add(int from,int to,int w){
edge[num]=Edge(from,to,w);
next[num]=head[from];
head[from]=num++;
}
struct HeapNode{
int dis;
int x;
HeapNode(int dis,int x):dis(dis),x(x){}
bool operator < (const HeapNode &rhs) const{
return dis>rhs.dis;
}
};
int dijkstra(int n){
for(int i=0;i<n;i++)
dis[i]=INF;
dis[0]=0;
priority_queue<HeapNode> Q;
Q.push(HeapNode(dis[0],0));
while(!Q.empty()){
HeapNode x=Q.top();
Q.pop();
int u=x.x;
if(vis[u])
continue;
vis[u]=true;
for(int i=head[u];i!=-1;i=next[i]){
Edge &e=edge[i];
if(dis[e.to]>dis[u]+e.w){
dis[e.to]=dis[u]+e.w;
Q.push(HeapNode(dis[e.to],e.to));
}
}
}
return dis[n-1];
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
num=0;
memset(head,-1,sizeof(head));
while(m--){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
x--,y--;
add(x,y,w);
}
int res=dijkstra(n);
if(res==INF)
printf("arbitrary");
else//源点到终点的最大值
printf("%d\n",res);
return 0;
}
2.SPFA 求解
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define INF 0x3f3f3f3f
#define N 10001
struct Edge{
int from;
int to;
int w;
Edge(){}
Edge(int from,int to,int w):from(from),to(to),w(w){}
}edge[N];
int head[N],next[N],num;
int dis[N];
int outque[N];//记录出队次数
bool vis[N];
void init(){
num=0;
memset(head,-1,sizeof(head));
}
void add(int from,int to,int w){
edge[num]=Edge(from,to,w);
next[num]=head[from];
head[from]=num++;
}
void SPFA(int s,int n){
int res=0;
memset(vis,false,sizeof(vis));
memset(outque,0,sizeof(outque));
for(int i=1;i<n;i++)
dis[i]=INF;
dis[s]=0;
vis[s]=true;
queue<int> Q;
Q.push(s);
while(!Q.empty()){
int x=Q.front();
Q.pop();
vis[x]=false;
outque[x]++;
if(outque[x]>n-1){//如果出队次数大于n,说明存在负环
res=-1;
break;
}
for(int i=head[x];i!=-1;i=next[i]){
Edge &e=edge[i];
if(dis[e.to]>dis[x]+e.w){
dis[e.to]=dis[x]+e.w;
if(!vis[e.to]){
vis[e.to]=true;
Q.push(e.to);
}
}
}
}
if(res==-1)//出现负环,不存在可行解
printf("-1\n");
else if(dis[n]==INF)//可取任意值
printf("arbitrary\n");
else//源点s到终点的最大值
printf("%d\n",dis[n]);
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
init();
for(int i=0;i<m;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);//建边x-y<=w
}
//虚拟节点0号到各点的边
//for(int i=1;i<=n;i++)
//add(0,i,0);
SPFA(1,n);//求源点s到终点的最大值,若添加虚拟节点0号后,需改为SPFA(0,n+1)
}
return 0;
}