猫史档案馆


【C++ / 用图论思想解n元不等式组】差分约束系统

用户:爵士OIer爵士OIer查看:4 回复:6 评论:4 创建时间:2020-05-03T11:05:29


 

 

 

 

1.差分约束系统(system of difference constraints)

 

 

 · 差分约束是求解n元一次特殊不等式组的一种方法。

 · 差分约束系统包含n个变量和m个约束条件,每个约束条件都是一个关于两个变量的一次不等式,每个不等式形如X[i]-X[j]<=A[k],其中1<=i,j<=n,1<=k<=m,X[i]、X[j]为变量,A[k]为常数。

 · 引理:若{Xi}为差分约束系统的一组解,v为常数,那么{Xi喵}也是一组解。这个不等式组要么无解,要么有无数组解。

 · 差分约束可以转化为图论中的单源最短路问题求解。

 

总的来说,含有m个形如

x_i−x_j≤k

的形式的约束条件的n元不等式组成为差分约束系统(system of difference constraints.)

 


 

2.具体求法

 

 

概说

 

 · 差分约束系统中的每个不等式都与最短路中的三角形不等式 dist[v]<=dist[u]+edge(u,v) 形似。可以把差分约束系统转化成一张图。

 · 把变量Xi看做有向图中的点i,对不等式X[i]-X[j]<=A[k]从j向i连一条有向边,边权为A[k]。建立一个源点,向每个点连一条有向边,边权为0。从源点出发求单源最短路,X[i]=dist[i]就是一组可行解。有负环说明无解。

 · 若对X[i]有范围限制,可用X[i]与源点之间的约束表示。

 · 不等式>=号时用最长路解决,此时有正环说明无解。

 

 

建图

 

 

比如

X[1]-X[5] <= 1

可以得出

X[1]<= X[5] + 1

同时源点到点i的距离小于等于源点到点j的距离加上点j到点i的距离,

Dis(i) <= Dis[j] + edge(j, i)

因此对于每一对

X[i]-X[j]<=A[k],

就从j到i连一条权是A[k]的边即可。

 

那么为什么有负环就无解?

从跑图的角度来看,就会一直跑下去。我们再来看看恢复到原来的不等式里。

比如:

center_image

看左图。

由这个图,可以列出:

X[1]-X[2]<0

X[2]-X[3]<0

X[3]-X[1]<0

很容易得出,X[1]<X[2],X[2]<X[3],即X[1]<X[3]

那么从第三个式子又能看出什么呢?X[1]>X[3].

所以不等式无解.

 

 

 

 

 

 


 

 

3.例题讲解

 

 

[Usaco2005 dec]Layout 排队布局

 

题目描述

当排队等候喂食时,奶牛喜欢和它们的朋友站得靠近些。FJ有N头奶牛,编号从1到N,沿一条直线站着等候喂食。奶
牛排在队伍中的顺序和它们的编号是相同的。因为奶牛相当苗条,所以可能有两头或者更多奶牛站在同一位置上。即
使说,如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。一些奶牛相互间存有好
感,它们希望两者之间的距离不超过一个给定的数L。另一方面,一些奶牛相互间非常反感,它们希望两者间的距离
不小于一个给定的数D。给出ML条关于两头奶牛间有好感的描述,再给出MD条关于两头奶牛间存有反感的描述。你
的工作是:如果不存在满足要求的方案,输出-1;如果1号奶牛和N号奶牛间的距离可以任意大,输出-2;否则,计算
出在满足所有要求的情况下,1号奶牛和N号奶牛间可能的最大距离。

 

输入描述

第1行:三个空格分隔的整数:N、ML和MD。ML+1:每行包含3个以空格分隔的正整数:A、B、D,其中1 <= A < B <=
n。* ML + 2行. .ML+MD+1:每行包含三个以空格分隔的正整数:A、B、D,其中1 <= A < B <= N.奶牛A和奶喵之间
的距离必须至少为D。

 

输出描述

第1行:单个整数。如果不可能排列,输出-1。如果母牛1和牛N可以任意地相隔很远,输出-2。否则,输出奶牛1和N
之间的最大可能距离。

 

输入样例

4 2 1
1 3 10
2 4 20
2 3 3

 

输出样例

27

 

数据规模

2<=N<=1000

1<=ML,MD<=10000,1<=L,D<=1000000

 

一道裸的差分约束系统

#include<bits/stdc++.h>
using namespace std;
int n,ml,喵,a,b,c,fst[10100],nex[50010],v[50010],w[50010],cnt,vis[10100],dis[10100],tim[10100];
queue<int> q;
void add(int a,int b,int c){
    nex[++cnt]=fst[a];fst[a]=cnt;v[cnt]=b;w[cnt]=c;
    return ;
}
int spfa(int k){
    memset(dis,0x7f/3,sizeof(dis));
    memset(vis,0,sizeof(vis));
    memset(tim,0,sizeof(tim));
    q.push(k);dis[k]=0;vis[k]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();tim[u]++;
        vis[u]=0;
        if(tim[u]>n)return -1;
        for(int i=fst[u];i!=-1;i=nex[i]){
            if(dis[v[i]]>dis[u]+w[i]){
                dis[v[i]]=dis[u]+w[i];
                if(!vis[v[i]]){
                    q.push(v[i]);
                    vis[v[i]]=1;
                }
            }
        }
    }
    if(dis[n]>1e8)
    return -2;
    return dis[n];
}
int main(){
    memset(fst,-1,sizeof(fst));
    cin>>n>>ml>>喵;
    for(int i=1;i<=ml;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    for(int i=1;i<=喵;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(b,a,-c);
    }
    for(int i=1;i<n;i++)add(i+1,i,0);
    for(int i=1;i<=n;i++)add(0,i,0);
    int sp=spfa(0); 
    if(sp<=-1){
        cout<<sp;
        return 0;
    }
    else cout<<spfa(1);
    return 0;
}

 

 

4.模板代码

最后附上模板代码

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;

int n, m, cnt, Number[N]/*统计每一个点存入的次数*/, dist[N] /*距离*/, head[N];
bool visit[N];

struct Node {
    int u, v, nxt;
} a[N];

inline void init () {
    memset (head, -1, sizeof (head));
    memset (dist, -1, sizeof (dist)); // 这里一定要初始化!否则会凉凉
    Number[0] = 1; // 存源点 x0 次数为 1
    dist[0] = 0; // 源点距离为 0
    visit[0] = true; // 标记源点 x0 已在队列中
}

inline void add (int u, int v, int w) { // 加边板子不解释?
    a[++ cnt].u = v;
    a[cnt].v = w;
    a[cnt].nxt = head[u];
    head[u] = cnt;
}

inline bool SPFA () { // SPFA 板子,稍改动
    queue <int> q;
    q.push (0); // 存入最初的源点 x0
    while (!q.empty ()) {
        int u = q.front (); q.pop (); // 出队
        visit[u] = false;
        for (register int i = head[u]; ~i; i = a[i].nxt) { // 遍历与 u 联通的所有点
            int v = a[i].u;
            if (dist[v] < dist[u] + a[i].v) { // 进行松弛操作
                dist[v] = dist[u] + a[i].v;
                //由于更新了节点,所以后续以这个为基础的最短路也要更新,所以如果已经在队列里面了就不用加入,不在的话就加入更新后续节点
                if (!visit[v]) {
                    q.push (v);
                    visit[v] = true; // 标记这个点在队列中了
                    ++ Number[v]; // 存入点 v 的次数加一
                    if (Number[v] > n) return false; // 如果这个点加入超过了 n 次就说明存在负环,直接无解返回 false
                }
            }
        }
    }
    return true;
}

inline int read () { // 快读就不解释了
    int Res = 0, f = 1; char ch = getchar ();
    while (!isdigit (ch) && ch ^ '-') ch = getchar ();
    if (ch == '-') {f = -1; ch = getchar ();}
    while (isdigit (ch)) {Res = (Res << 1) + (Res << 3) + (ch ^ '0'); ch = getchar ();}
    return Res * f;
}

inline void input () { // 输入
    n = read ();
    m = read ();
    for (register int i = 1; i <= m; ++ i) {
        int u=read();
        int v=read();
        int w=read();
        add (u, v, -w); // 存负边以负的形式求最长路
    }
    for (register int i = 1; i <= n; ++ i) add (0, i, 0); // 把源点 x0 与所有点连接
}

int main () {
    init ();
    input ();
    if (SPFA()){ //有解
        for(register int i=1;i<=n;++i)printf("%d ",dist[i]);
    } else printf ("NO\n"); //无解
    return 0;
}


回复

上一页1 页 / 共 1下一页
爵士OIer爵士OIer

模板代码懒得自己打了

点赞0


评论


爵士OIer爵士OIer

?所以

有人吗()

点赞0


评论


爵士OIer爵士OIer

所以快来人()

点赞0


评论


0孤皇00孤皇0

额额

点赞0


评论


爵士OIer爵士OIer

我这个没加精,Max那连代码都没的加了,见识()

点赞0


评论


星空之忆星空之忆

?你加入帅哥工作室了

点赞0


评论