浅谈差分约束系统

更好的阅读体验

差分约束系统

前言

真的好久好久都没打过这个算法了。当时学的时候学得不明不白,又不写总结、又不刷题(我都不知道自己咋想的),所以今天刷图论题的时候,发现一车子的差分约束都没打过。

所以,重学,开写!

差分约束系统是什么

不要被他名字的学术性吓到了,这个“系统”字面意思理解就行,不是什么高深庞大的东西。

一个差分约束系统形如:

已知

{ x c 1 − x c 1 ′ ≤ y 1 x c 2 − x c 2 ′ ≤ y 2 ⋯ x c m − x c m ′ ≤ y m \begin{cases} x_{c_1}-x_{c'_1}\leq y_1 \\x_{c_2}-x_{c'_2} \leq y_2 \\ \cdots\\ x_{c_m} - x_{c'_m}\leq y_m\end{cases} xc1xc1y1xc2xc2y2xcmxcmym

求这个不等式组任意的一组解。

其实说白了,就是 n n n 个变量, m m m 个约束条件( x i − x j ≤ y k x_i-x_j\leq y_k xixjyk)。

怎么解决这个系统

怎么解这个不等式?

首先移项一手:

x i ≤ x j + y k x_i\leq x_j+y_k xixj+yk

你看,是不是和图论中的 d i s v ≤ d i s u + w i dis_v\leq dis_u+w_i disvdisu+wi 很像啊?

所以这个问题可以转换成一个最短路问题——把 j j j i i i 连一条权值为 y k y_k yk 的边。

可是这个图并不一定连通,所以我们建一个超级源点 0 0 0。带入到系统中,便是增加了 n n n 个约束条件: x i ≤ x 0 x_i\leq x_0 xix0。如此,整个系统得到完善。

然后就家常便饭,直接最短路。如果存在负环,不等式组无解;否则可以得到每个点的 d i s i dis_i disi。而不等式的一组可行解,正是 x i = d i s i x_i=dis_i xi=disi

负环怎么判

这里稍微讲一手。

首先为什么出现负环就无解?

因为最短路算法,就是不断走边权最小的边,以更新每个点到源点的最短距离。但如果存在一个环,且总权为负,那么就可以一直走、一直走,直至边权 = − ∞ =-\infin =

现在,原图共 n + 1 n+1 n+1 个点,如果不存在负环,那么最短路中每个点肯定只会进队一次。因为没有必要经过两次去松弛它的邻接点。

所以如果 n n n 轮松弛结束后仍然存在可松弛的边,那就一定存在负环。

在题目中如何用差分约束算法

如果在模版题,就不多说了。

我们对于题目中的关系,可以得到一些不等条件,再化简成一般形式 x i ≤ x j + y k x_i\leq x_j+y_k xixj+yk,就可以建立这样一个差分约束系统。

如果是相等情况怎么办?

简单,直接拆分成 x i ≤ x j + y k x_i\leq x_j+y_k xixj+yk x j ≤ x i + y k x_j\leq x_i+y_k xjxi+yk 这两条约束条件。

P5960【模板】差分约束

code

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

#define int long long

const int MAXN=1e4+5;

int n,m;
int dis[MAXN],cnt[MAXN];
bool vis[MAXN];
int su,en[MAXN],lt[MAXN],hd[MAXN],vl[MAXN];

void add(int u,int v,int w)
{
    en[++su]=v,vl[su]=w,lt[su]=hd[u],hd[u]=su;
}

bool SPFA(int s)
{
    queue<int> q;
    memset(dis,0x3f,sizeof(dis));
    vis[s]=1,dis[s]=0;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=hd[u];i;i=lt[i])
        {
            int v=en[i];
            if(dis[v]>dis[u]+vl[i])
            {
                dis[v]=dis[u]+vl[i];
                if(!vis[v])
                {
                    vis[v]=1,cnt[v]++;
                    if(cnt[v]>n) return 0;
                    q.push(v);
                }
            }
        }
        vis[u]=0;
    }
    return 1;
}

signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        add(0,i,0); // 超级源点
    for(int i=1,u,v,w;i<=m;i++)
    {
        scanf("%lld%lld%lld",&u,&v,&w);
        add(v,u,w); // 注意这里是 v --w-> u 
    }
    if(!SPFA(0))
        puts("NO");
    else
    {
        for(int i=1;i<=n;i++)
            printf("%lld ",dis[i]);
    }
    return 0;
}

参考文献

结尾

如果想看图论相关文章,刚好我 2 年前写了一篇。虽然年代久远,格式、文笔也很垃坤,但是得看且看吧。

溜去做题了。

如果你想问为什么有些题目用的差分约束是跑最长路,这个我还不能回答你(我也不知道),但是可以先放张图,等我学会后再回来更(当然也可能忘记了,如果你正在看,可以在评论区踢我)。

图:

(看起来是两种不同的方式罢了……)

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值