spfa算法_算法学习笔记(11): 差分约束

c551a6f79cf160c54026e4969039d39e.png

差分约束系统是下面这种形式的多元一次不等式组(

为已知量):

(每个不等式称为一个约束条件,都是两个未知量之差小于或等于某个常数)

在算法竞赛中,很多题目会给出(或隐性地给出)一系列的不等关系,我们可以尝试把它们转化为差分约束系统来解决。


我们设

,移项得

观察这个不等式与最短路问题中的三角形不等式

的相似之处。利用这一点,我们可以把它转化为一个
图论问题。也就是说,对于每一个
,我们都从
建一条边,边权为

这样建出的有向图,它的每个顶点都对应差分约束系统中的一个未知量,源点到每个顶点的最短路对应这些未知量的,而每条对应一个约束条件

8d8c351b3500a413cb05071d0a2afab6.png

那么问题来了,既然是最短路,源点在哪里呢?

实际上取哪个点为源点是无关紧要的,但是,有时候我们得到的图不是连通的,这样求出来的结果很容易出现INF。为了避免这种情形,我们习惯人为地增加一个超级源点

例如我们现在人为地新增一个0号点(或n+1号点),从它向所有顶点连一条边权为0的边:

d57cf828f9b13ba083d54c8a84a2860b.png

现在我们以0号点为源点求各点的最短路即可。注意,这相当于了添加了以下约束条件:

由于

对应的是
,而
,可知所有未知量均小于等于0(反映在图形上是所有点的最短路均小于等于0)。这是为什么?实际上我们往往要的是非负解呀。

因为这求出的只是一组解,通过简单的数学计算可知,在符合差分约束系统的一组解上加上或减去同一个数,得到的解同样符合原系统。例如,上文例子求得的结果为

,然而
也是一组解。

其实我们可以把

设为另一个数
而不是0(或者把从0号点连向各点的边权设为
),那么我们得到的便是满足
的一组解。实际上,可以证明,它们是满足
最大解(每个变量取到能取到的最大值)。(可参见这篇文章和这篇博客)

那么如何求满足

最小解呢?只需要求 最长路就行了。最长路满足三角形不等式
,所以相应的差分约束系统需要把小于等于符号换成大于等于符号。对于Bellman-Ford或SPFA算法来说,只需要初始化为-INF而不是INF,然后把比较符号颠倒一下即可。

现在给出一道模板题的完整代码:

(洛谷P5960【模板】差分约束算法)

题目描述
给出一组包含 m 个不等式,有 n 个未知数的形如:

的不等式组,求任意一组满足这个不等式组的解。 输入格式
第一行为两个正整数 n,m,代表未知数的数量和不等式的数量。
接下来 m 行,每行包含三个整数
,代表一个不等式
输出格式
一行,n 个数,表示
的一组可行解,如果有多组解,请输出任意一组,无解请输出
NO
#include <bits/stdc++.h>
#define MAXN 5005
#define MAXM 10005
using namespace std;
int read()
{
    int ans = 0, sgn = 1;
    char c = getchar();
    while (!isdigit(c))
    {
        if (c == '-')
            sgn *= -1;
        c = getchar();
    }
    while (isdigit(c))
    {
        ans = ans * 10 + c - '0';
        c = getchar();
    }
    return ans * sgn;
}
int cnt_edge, head[MAXN];
struct
{
    int to, next, w;
} edges[MAXM];
void add_edge(int from, int to, int w)
{
    edges[++cnt_edge].next = head[from];
    edges[cnt_edge].to = to;
    edges[cnt_edge].w = w;
    head[from] = cnt_edge;
}
bool inqueue[MAXN];
int cnt[MAXN], dis[MAXN];
queue<int> Q;
bool SPFA(int s, int n)
{
    memset(dis, 127, sizeof(dis));
//  memset(dis, -127, sizeof(dis));
    dis[s] = 0;
    Q.push(s);
    while (!Q.empty())
    {
        int p = Q.front();
        if (cnt[p] > n)
            return false;
        Q.pop();
        inqueue[p] = false;
        for (int eg = head[p]; eg != 0; eg = edges[eg].next)
        {
            int to = edges[eg].to;
            if (edges[eg].w + dis[p] < dis[to])
//          if (edges[eg].w + dis[p] > dis[to])
            {
                dis[to] = edges[eg].w + dis[p];
                if (!inqueue[to])
                {
                    Q.push(to);
                    inqueue[to] = true;
                    cnt[to]++;
                }
            }
        }
    }
    return true;
}
int main()
{
    int n = read(), m = read();
    for (int i = 0; i < m; ++i)
    {
        int x = read(), y = read(), w = read();
        add_edge(y, x, w);
//      add_edge(x, y, -w);
    }
    for (int i = 1; i <= n; ++i)
        add_edge(0, i, 0);
    if (SPFA(0, n))
    {
        for (int i = 1; i <= n; ++i)
            printf("%d ", dis[i]);
    }
    else
        puts("NO");
    return 0;
}

这里用到了SPFA判负环,如果存在负环,最短路无解,则原不等式组也无解。


在实际问题中,不等关系不一定这么简单,但有些不等关系可以被转化为

,例如:

1.

:转化为

2.

:转化为

3.

:转化为
(整数的情形,下同)

4.

:转化为

而且题目中还可能隐含一些不等关系,比如:

(洛谷P1250 种树)

题目描述
一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民想在门前种些树并指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。
写一个程序完成以下工作: 输入格式
第一行包含数据N,区域的个数(0<N≤30000);
第二行包含H,房子的数目(0<H≤5000);
下面的H行描述居民们的需要:B E T,0<B≤E≤30000,T≤E-B+1。 输出格式
输出文件只有一行写有树的数目

如果以每一块上树的数量为变量,是很难下手的,但我们可以转而使用前缀和。那么题目给出的条件就可以被转化为

。但仅仅是这样是不够的,还要注意到,题目要求每一块上只能种一棵树,所以我们有
。以这些约束条件建图求最长路就可以解决了。

https://zhuanlan.zhihu.com/p/105467597​zhuanlan.zhihu.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值