初识差分约束

对于差分约束理解

差分约束解决了一类不等式组的解的问题,如

(0) x 1 − x 0 ≤ a x_1 - x_0 \leq a \tag{0} x1x0a(0)
(1) x 2 − x 1 ≤ b x_2-x1\leq b \tag{1} x2x1b(1)
(2) x 2 − x 0 ≤ c x_2-x_0\leq c \tag{2} x2x0c(2)
然后我们需要求的是 x 2 − x 0 x_2-x_0 x2x0的最大值 M a x Max Max
经过观察我们可以发现将 ( 0 ) + ( 1 ) (0)+(1) (0)+(1)得到
(3) x 2 − x 0 ≤ a + b \color{red} x_2-x_0 \leq a+b \tag{3} x2x0a+b(3)
(2) x 2 − x 0 ≤ c x_2-x_0 \leq c \tag{2} x2x0c(2)
综上
M a x = m i n { a + b , c } Max = min\{ a+b,c\} Max=min{a+b,c}
如果我们用几何的方式来表示这三个不等式的话我们会有新的发现
方向三角
M a x = m i n { a + b , c } Max = min\{ a+b,c\} Max=min{a+b,c}不就是从 0 0 0 1 1 1的最短距离吗
综上我们可以得出差分约束的一种解法-利用最短路算法


差分约束系统与最短路算法

现在我们将目光放在如何构造差分约束系统,并使用最短路算法解决。
对于任意不等式组
x 1 − x 0 ≤ a x 2 − x 1 ≤ b x 2 − x 0 ≤ c x_1 - x_0 \leq a \\ x_2-x1\leq b \\ x_2-x_0\leq c x1x0ax2x1bx2x0c
将其写成如下形式
x i − x j ≤ a i x_i-x_j \leq a_i xixjai
并对每一个不等式建立有向边 j → i j\rightarrow i ji边权为 a i a_i ai
x u − x v x_u - x_v xuxv的最大值 = > => =>即求 v → u v\rightarrow u vu最短路径


最大值与最小值的转换

当我们需要求的是 x u − x v x_u - x_v xuxv的最小值的时候该怎么办
x u − x v ≥ M i n x_u-x_v \geq Min xuxvMin
需要将差分约束系统所有不等式组变为
x j − x i ≥ − a i x_j-x_i \geq -a_i xjxiai
并对每一个不等式建立有向边 i → j i\rightarrow j ij边权为 − a i -a_i ai
x u − x v x_u - x_v xuxv的最小值 = > => => v → u v\rightarrow u vu最长路径
这种方法和上述最短路法是等价的


最短路算法SPFA

由于差分约束系统用存在负权边,传统的最短贪心策略Dijkstra算法并不能处理负权边。
再者由于负权边会出现负权环,SPFA算法可以对负权图进行判断。
SPFA算法的原理:利用队列不断从队列中选取出结点出来松弛,直到队列为空为止。
基本模板

bool SPFA(int from,int to) {
    memset(vis,0,sizeof(vis));
    memset(cnt,0,sizeof(cnt));
    for(int i=0;i<maxn;i++) dist[i] = inf;
    vis[from] = true;
    dist[from] = 0;
    queue<int> qu;
    qu.push(from);
    cnt[from]=1;
    while(!qu.empty()) {
        int u = qu.front();qu.pop();
        vis[u] = false;
        for(int i=head[u];~i;i=edge[i].next) {
            int v = edge[i].v;
            if(dist[v] > dist[u] + edge[i].cost) {
                dist[v] = dist[u] + edge[i].cost;
                if(!vis[v]) {
                    vis[v] = true;
                    qu.push(v);
                    if(++cnt[v]>n) return false;
                }
            }
        }
    }
    return true;
}

v i s [ ] vis[] vis[]表示结点是否在队列中
d i s t [ ] dist[] dist[]记录了源点到其他点的最短路径
c n t [ ] cnt[] cnt[]用于判断是否存在负权环,当一个结点进入队列超过 n n n次,即存在负权环


例题

例题1:ZOJ 2770

题意
给出 n n n个点表示 n n n个军营, C i C_i Ci表示第i个军营可容纳的士兵的最大值,接着给出 m m m条边 ( i , j , k ) (i,j,k) (i,j,k)表示从第 i i i到第 j j j个军营拥有的最少士兵数。求在满足以上条件下最少有多少士兵
题解
差分系统
设每个军营人数为 A i A_i Ai,军营容量为 C i C_i Ci,军营人数的前缀和为 S n S_n Sn

  1. A i ≤ C i Ai \leq Ci AiCi 写成前缀和的形式
    S i − S i − 1 &lt; = C i S_i - S_{i-1} &lt;= C_i SiSi1<=Ci

  2. 从第 i i i个军营到第 j j j个军营的人数和至少为 k k k
    S j − S i − 1 ≥ k S_j - S_{i-1} \geq k SjSi1k 转换为
    S i − 1 − S j ≤ − k S_{i-1} - S_j \leq -k Si1Sjk

  3. 每个军营都必须有人 A i ≥ 0 A_i \geq 0 Ai0
    S i − S i − 1 ≥ 0 S_i - S_{i-1} \geq 0 SiSi10转换为
    S i − 1 − S i ≤ 0 S_{i-1} - S_i \leq 0 Si1Si0
    目标:求解 S n − S 0 S_n - S_0 SnS0的最小值,设最小值为 M M M
    S n − S 0 ≥ M S_n - S_0 \geq M SnS0M
    转换
    S 0 − S n ≤ − M S_0 - S_n \leq -M S0SnM

    于是我们只需要求解从点 n n n到点 0 0 0的最短路径 − M -M M
    去负号即为 M M M

    :存在负环则为不可估计无解

综上得到的差分约束系统为
S i − S i − 1 &lt; = C i S i − 1 − S j ≤ − k S i − 1 − S i ≤ 0 S_i - S_{i-1} &lt;= C_i \\ S_{i-1} - S_j \leq -k \\ S_{i-1} - S_i \leq 0 SiSi1<=CiSi1SjkSi1Si0
T a r g e t : S 0 − S n ≤ − M Target:S_0 - S_n \leq -M Target:S0SnM
代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 1e3+10;
const int maxm = 1e5+10;
struct Edge {
    int v;
    ll cost;
    int next;
    Edge(){}
    Edge(int _v,ll _c,int _n):v(_v),cost(_c),next(_n){}
};
Edge edge[maxm];
int n,m;
int head[maxn],tot;
bool vis[maxn]; // 是否在队列
int cnt[maxn]; // 每个点的入队次数
ll dist[maxn]; // 最短路径
ll cot[maxn];
inline void addedge(int u,int v,ll val) {
    edge[tot] = Edge(v,val,head[u]);
    head[u] = tot++;
}
bool SPFA(int from,int to) {
    memset(vis,0,sizeof(vis));
    memset(cnt,0,sizeof(cnt));
    for(int i=0;i<maxn;i++) dist[i] = inf;
    vis[from] = true;
    dist[from] = 0;
    queue<int> qu;
    qu.push(from);
    cnt[from]=1;
    while(!qu.empty()) {
        int u = qu.front();qu.pop();
        vis[u] = false;
        for(int i=head[u];~i;i=edge[i].next) {
            int v = edge[i].v;
            if(dist[v] > dist[u] + edge[i].cost) {
                dist[v] = dist[u] + edge[i].cost;
                if(!vis[v]) {
                    vis[v] = true;
                    qu.push(v);
                    if(++cnt[v]>n) return false;
                }
            }
        }
    }
    return true;
}
inline void init() {
    memset(head,-1,sizeof(head));tot = 0;
    memset(cot,0,sizeof(cot));
}

int main()
{
    while(~scanf("%d%d",&n,&m)) {
        init();
        ll cost;
        for(int i=0;i<n;i++) {
            scanf("%lld",&cost);
            cot[i+1] = cot[i] + cost;
            addedge(i,i+1,cost);
            addedge(i+1,i,0);
        }
        for(int i=0,x,y;i<m;i++) {
            scanf("%d%d%lld",&x,&y,&cost);
            addedge(y,x-1,-cost);
            //addedge(x-1,y,cot[y]-cot[x-1]);
        }
        bool flag = SPFA(n,0);
        if(flag) printf("%lld\n",-dist[0]);
        else printf("Bad Estimations\n");
    }
    return 0;
}
例题2:CSP201809-4 再卖菜
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值