差分约束系统

差分约束系统是线性规划问题的一个特例。k行n列的线性规划矩阵 A 的每一行包含一个1和一个-1,其余均为0。也就是说,每个约束条件都形如

xjxibk

单源最短路问题

这个问题可以从另一个角度来理解:给每个顶点 v 分配一个顶标δ(s,v),满足:
1. δ(s,v)δ(s,u)+w(u,v),(u,v)E
2. (u,v)E使δ(s,v)=δ(s,u)+w(u,v)

约束图

移项得

xjbk+xi

发现它和最短路问题中的三角不等式是一致的:

δ(s,v)δ(s,u)+w(u,v),(u,v)E

由此,我们建图如下,得到差分约束系统的一组可行解或判其无解:
添加额外的源点 v0 ,从 v0 向每个 xi 连一条有向边,边权为0。对于每个不等式 xjxibk ,连有向边 (vi,vj) w(vi,vj)=bk 。求以 v0 为源点的单源最短路,如果存在负圈,则无解;否则, xi=δ(s,vi),1in 是差分约束系统的一组可行解。

这样的一个图称为约束图

无解

为什么存在负圈意味着差分约束系统无解呢?我们来证明一下。

<v1,v2,...,vm> <script id="MathJax-Element-17" type="math/tex"> </script>是约束图中的一个负环, v1=vm 。相应地,差分约束系统中有不等式

x2x1w(v1,v2)x3x2w(v2,v3)...vmvm1w(vm1,vm)

全部加起来,有

0

这是不科学的。证毕。

转化

xjxibk<=>xixjbkxjxi<bk<=>xjxibk1xjxi>bk<=>xixjbk+1xi=xj<=>xixj and xjxixibk<=>xi0bk

解的特性

定理1:如果 (x1,x2,...,xn) 是一组可行解,那么 (x1+d,x2+d,...,xn+d) 也是一组可行解。
证明略。

定理2:由约束图得到的可行解满足 xi0
证明略。

定理3:由约束图得到的可行解与其他满足 xi0 的可行解相比,最大化 Σni=1xi
假设另有一组满足 yi0 的可行解 (y0,y1,y2,...,yn) ,为了统一起见,同样附加一个 y0=0 。考察源点到任意一点,不妨设其为 vn ,最短路径为 <v0,v1,v2,...,vn> <script id="MathJax-Element-4537" type="math/tex"> </script>。 x0y0 。假设 xiyi 成立,那么 xi+1=xi+w(vi,vi+1)yi+w(vi,vi+1)yi+1 。第一个等号是根据最短路的性质。由数学归纳法,对于所有 xi ,均有 xiyi 成立,因而最大化了 Σni=1xi


UPDATE 2016.9.10
和WZH学长讨论有没有更直观的证法,受到启发,如下:
x、y还是和上面一样。从 v0 vn ,有

xnxn1=w(vn1,vn)xn1xn2=w(vn2,vn1)...x1x0=w(v0,v1)


ynyn1w(vn1,vn)yn1yn2w(vn2,vn1)...y1y0w(v0,v1)

分别全部相加,得
xnx0=Σwyny0Σw

代入 x0=y0=0 ,得
xnyn

证毕。


定理4:由约束图得到的可行解最小化 max{xi}min{xi}
由定理1,我们可以“平移”解向量。这个变换不改变各个变量的相对大小。假设另有一组可行解 (y1,y2,...,yn) ,将它平移,使得 max{yi}=max{xi} 。由定理3的证明, min{xi}min{yi} ,故 max{xi}min{xi}max{yi}min{yi}

如果我们要最小化 Σni=1xi ,最大化 max{xi}min{xi} ,该怎么做呢?

把最短路改为最长路即可。

怎么求最长路呢?一种方法是把所有边权取反,用SPFA求最短路。另一种方法是直接修改SPFA松弛的条件。注意这里的最长路无权简单最长路的区别,所以它不是NP完全问题。

例题

Bzoj 2330 [SCOI2011]糖果

一开始TLE,上网搜索,得知这道题有两个坑:
1. 有一组数据是一条很长的链。不知道除了面向数据还有什么解决方法呢?
2. 有一组数据存在很大的负环。虽然这组数据存在负的自环,可以直接判掉……

关于负环的判定,我参考了lydrainbowcat的方法:记录最短路径的长度。

不良心的出题人。

#include <cstdio>
#include <queue>
#include <cctype>
#define NO_SOL() {puts("-1"); return 0;}
using namespace std;
typedef long long ll;
const int MAX_N = 100000, MAX_K = 100000;
int e_ptr = 1, n, k, head[MAX_N+1], d[MAX_N+1];
ll dis[MAX_N+1];
bool inq[MAX_N+1];
struct Edge {
    int v, next;
    ll w;
} E[MAX_K*2+1];
inline void add(int u, int v, ll w)
{
    E[e_ptr] = (Edge){v, head[u], w};
    head[u] = e_ptr++;
}

bool SPFA()
{
    queue<int> Q;

    for (int i = 1; i <= n; ++i) {
        inq[i] = true;
        dis[i] = -1;
        Q.push(i);
    }

    int u;
    while (!Q.empty()) {
        u = Q.front();
        Q.pop();
        inq[u] = false;
        for (int i = head[u]; i; i = E[i].next) {
            int v = E[i].v;
            ll upd = dis[u] + E[i].w;
            if (dis[v] > upd) {
                dis[v] = upd;
                d[v] = d[u] + 1;
                if (d[v] > n)
                    return false;
                if (!inq[v]) {
                    inq[v] = true;
                    Q.push(v);
                }
            }
        }
    }
    return true;
}

template<typename T>
inline void read(T& x)
{
    x = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c)) {
        x = x*10 + c - '0';
        c = getchar();
    }
}

int main()
{
    read(n);
    read(k);
    int x, a, b;
    for (int i = 0; i < k; ++i) {
        read(x);
        read(a);
        read(b);
        switch (x) {
            case 1: // a = b
            add(a, b, 0);
            add(b, a, 0);
            break;
            case 2: // a < b
            if (a == b)
                NO_SOL();
            add(a, b, -1);
            break;
            case 3: // a >= b
            add(b, a, 0);
            break;
            case 4: // a > b
            if (a == b)
                NO_SOL();
            add(b, a, -1);
            break;
            case 5: // a <= b
            add(a, b, 0);
        }
    }

    // 求最长路,边权取负后求最短路
    if (!SPFA())
        NO_SOL();
    ll ans = 0;
    for (int i = 1; i <= n; ++i)
        ans -= dis[i];
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值