洛谷题单指南-最短路-P5960 【模板】差分约束

原题链接:https://www.luogu.com.cn/problem/P5960

题意解读:n个未知数,m个不等式,求一组可能解。

解题思路:

1、概念

差分约束系统是由一组形如xj-xi <= ck的不等式组成的系统,其中xi xj是变量,ck是常数。其目标是求解这个不等式组,找到一组满足所有不等式的变量取值。

2、建模

通过将不等于移项,变成形如xj <= xi + ck的形式,对于每一个不等式,通过在i->j之间连一条长度为ck的边,如果从某个起点到各个点存在一条最短路,则必然满足xj <= xi + ck,因为如果存在xj > xi + ck,在计算最短路过程中,必然会被松弛:if(xj > xi + ck) xj = xi + ck,最终一定满足x<= xi + ck

因此,求解不等式组的一组可能解,就变成了求所有点的最短路径,未知数xi的可能解就是dist[i]。

3、起点

要计算所有点的最短路,必须找到合适的起点,也就是从起点可以达到所有点,如果无法明确找到这样的点,不妨设0为起点,dist[0]=0,即x0=0,

从0点到所有点建一条长度为0的边,再跑一遍SPFA算法,如果不出现负环,则一组可能的解就是所有点的最短路dist[i]。

4、无解

如果在SPFA计算中出现负环,说明存在一个环(x1->x2->x3->x1)中的点有这样的关系:x2<=x1+c1, x3<=x2+c2, x1<=x3+c3,三个式子相加得到0<=c1+c2+c3,但是负环说明c1+c2+c3<0,产生矛盾,因此不等式无解。

5、变形

为了统一为最短路和负环计算,可以将不等式的关系也进行统一,

  • 如果出现A = B,可以转化为A <= B, B <= A
  • 如果出现A < B,可以转换为A + 1 <= B
  • 如果出现A > B,可以转化为B + 1 <= A
  • 如果出现A >= B,可以转化为B <= A

如果是要计算最长路和正环,则不等式的<=要变换成>=。

6、求最大值

  • 如果是求某个未知数xi的最大值, 对于从0到i的路径上的所有边的不等式进行相加:

    x1 <= x0

    x2 <= x1 + c1

    ...

    xi <= xi-1+ci-1

    得到xi <= c1+c2+...+ci-1

    要得到xi的最大值,必须求出所有可能的c1+c2+...+ci-1的最小值,也就是从0到i的最短路。

  • 如果是求某两个未知数xj-xi差值的最大值,同样对于i到j路径上的所有边的不等式进行相加,不难得出结论是求从i到j的最短路。

7、求最小值

要求最小值,不等式关系需要调整为形如:xj >= xi + ck

同样建立从i到j权值为ck的边,然后跑最长路,无解对应存在正环。

100分代码:

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

const int N = 5005, M = 10005;
int h[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N];
bool vis[N];
int n, m;

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));
    queue<int> q;
    q.push(0);
    dist[0] = 0;
    while(q.size())
    {
        int u = q.front(); q.pop();
        vis[u] = false;
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if(dist[v] > dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                cnt[v] = cnt[u] + 1;
                if(cnt[v] >= n + 1) return true; //存在负环
                if(!vis[v])
                {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return false;
}

int main()
{
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    while(m--)
    {
        int v, u, w;
        cin >> v >> u >> w;
        add(u, v, w);
    }
    for(int i = 1; i <= n; i++) add(0, i, 0); //虚拟源点0到所有点建立一条权值为0的边
    if(spfa()) cout << "NO";
    else for(int i = 1; i <= n; i++) cout << dist[i] << " ";
    return 0;
}

 

posted @ 2025-04-03 16:33  hackerchef  阅读(69)  评论(0)    收藏  举报