图论 —— 差分约束系统

【概述】

如果一个系统由 n 个变量 m 个约束条件组成,形成 m 个形如 a_i-a_j \leqslant k 的不等式,其中 1\leqslant i,j \leqslant n,k 是常数,则称这 m 个不等式为差分约束系统(system of difference constraints),亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。

如下图,就是一个差分约束系统

\left\{\begin{matrix}x_1-x_0\leqslant 0 & \\ x_2-x_0\leqslant 3 & \\ x_3-x_1\leqslant 7 & \\ x_4-x_2\leqslant 9 & \end{matrix}\right.

【求解方法】

差分约束系统的求解可以转化为图论的最短路来解决。

对于三角不等式,有:

  • 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;
}

【例题】

  1. Candies(POJ-3159)(Dijkstra)点击这里
  2. Layout(POJ-3169)(SPFA)点击这里
  3. Is the Information Reliable?(POJ-2983)(SPFA+超级源点)点击这里
  4. King(POJ-1364)(SPFA+超级源点)点击这里
  5. Instrction Arrangement(HDU-4109)(SPFA+超级源点)点击这里
  6. Schedule Problem(HDU-1534)(SPFA+超级源点)点击这里
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值