差分约束

差分约束

(1)求不等式组的可行解

原点需要满足的条件:从原点出发,一定可以走到所有的边。
(这一点必须要满足,否则会遗漏某些不等式)
步骤

  • 先将每个不等式Xi <= Xj + Ck,转化成一条从Xj走到Xi,长度为Ck的一条边。
  • 找一个超级原点,保证这个原点一定可以遍历所有边。
  • 从原点求一遍单源最短路:
    1 .如果存在负环,则原不等式组一定无解。
    2 .如果不存在负环,则dist[i]就是原不等式组的一个可行解。

根据不等号的方向,建图时注意方向。

(2)如何求最大值或者最小值(这里的最值指的是每个变量的最值)

结论:如果要求最小值,则应该求最长路;如果要求最大值,则应该求最短路。

问题:如何转化Xi <= C,其中C是一个常数,这样的不等式?
方法:建立一个超级原点0,然后建立0->i的边,长度为C。

以求Xi的最大值为例:求所有从Xi出发,构成的不等式链Xi <= Xj + C1 <= Xk + C1 + C2 <= … <= C1 + C2 + C3 +…(右边不能含有变量,否则无法求出最值)所计算出的上界,最终Xi的最大值等于所有上界的最小值(这里就可以得出求最大值用最短路的结论)。

下面根据一道例题来模拟一下上述过程:

Acwing1169.糖果

幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 K 个要求。
幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N,K。
接下来 K 行,表示分配糖果时需要满足的关系,每行 3 个数字 X,A,B。
如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。
小朋友编号从 1 到 N。
输出格式
输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。
数据范围
1 ≤ N < 1 0 5 , 1 ≤ K ≤ 1 0 5 , 1 ≤ X ≤ 5 , 1 ≤ A , B ≤ N 1≤N<10^5, 1≤K≤10^5, 1≤X≤5, 1≤A,B≤N 1N<105,1K105,1X5,1A,BN

yxc老师的分析过程:
根据题意我们可以判断出这是一道差分约束的题目,题目给了我们五个条件,并要求最小值,根据上述讨论我们可以根据这些不等式来建图,然后找一个超级原点,因为题干中说每个小朋友都可以都要分到糖果,所以我们可以假设一个超级原点0,从这点向其他所有点连一条边,长度为1(即代表每个小朋友都可以分到糖果),这同时保证了超级原点可以走到所有边,因为要求最小值,所以我们走一遍最长路即可,图中有正环的情况代表无解。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e5+100, M = 3 * N;

int n,m;
LL dist[N];
int q[N],cnt[N];
int h[N],e[M],ne[M],w[M],idx;
bool st[N];

void add(int a,int b,int c)
{
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}

bool spfa()
{
    memset(dist,-0x3f,sizeof dist);//求最长路,初始化成-∞
    dist[0] = 0; //超级原点
    
    int hh = 0,tt = 0;
    q[tt++] = 0;
    st[0] = true;
    while(hh != tt)
    {
        int t = q[--tt];  //判断负环超时的话,可以将队列换成栈
        st[t] = false;
        
        for(int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if(dist[j] < dist[t] + w[i]) //求最小值应求最长路
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n+1) return true;//加上超级原点
                if(!st[j])
                {
                    q[tt++] = j;
                    st[j] = true;
                }
            }
        }
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    
    memset(h,-1,sizeof h);
    while(m--)
    {
        int x,a,b;
        scanf("%d%d%d",&x,&a,&b);
        
        if(x == 1) add(a,b,0),add(b,a,0);
        else if(x == 2) add(a,b,1);
        else if(x == 3) add(b,a,0);
        else if(x == 4) add(b,a,1);
        else add(a,b,0);
    }
    
    //题目要求每个小朋友都会分到糖果,设定一个超级原点,向每个点都连一条边
    //超级原点可以到达所有边,这也保证了它可以到达所有边,满足超级原点的要求
    for(int i = 1; i<=n; i++) add(0,i,1);
    
    if(spfa()) puts("-1");  //如果存在正环,则无解
    else
    {
        LL ans = 0;
        for(int i = 1; i<=n; i++) ans += dist[i];
        printf("%lld\n",ans);
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值