• 差分约束,BFS-SPFA 与 DFS-SPFA 的优劣


    差分约束系统

    差分约束系统 是一种特殊的 $n$ 元一次不等式组,它包含 $n$ 个变量 $x_1,x_2,...,x_n$ 以及 $m$ 个约束条件,每个约束条件是由两个其中的变量作差构成的,形如 $x_i-x_jleq c_k$ ,其中 $c_k$ 是常数(可以是非负数,也可以是负数)。我们要解决的问题是:求一组解 $x_1=a_1,x_2=a_2,...,x_n=a_n$ ,使得所有的约束条件得到满足,否则判断出无解。

    差分约束系统中的每个约束条件 $x_i-x_jleq c_k$ 都可以变形成 $x_i leq x_j + c_k$ ,这与单源最短路中的三角形不等式 $dist[y]leq dist[x]+z$ 非常类似。因此,我们可以把每个变量 $x_i$ 看作图中的一个结点,对于每个约束条件 $x_i-x_jleq c_k$ ,从结点 $j$ 向结点 $i$ 连一条长度为 $c_k$ 的有向边。

    设 $dist[0]=0$ 并向每一个点连一条边,跑单源最短路,若图中存在负环,则给定的差分约束系统无解,否则,$x_i=dist[i]$ 为该差分约束系统的一组解。

    注意到,如果 ${a_1,a_2,...,a_n}$ 是该差分约束系统的一组解,那么对于任意的常数 $d$ ,${a_1+d,a_2+d,...a_n+d}$ 显然也是该差分约束系统的一组解,因为这样作差后 $d$ 刚好被消掉。

    一般使用 Bellman-Ford 或队列优化的 Bellman-Ford(俗称 SPFA ,在某些随机图跑得很快)判断图中是否存在负环,最坏时间复杂度为 $O(nm)$。

    BFS-SPFA 与 DFS-SPFA 的优劣

    SPFA 有 BFS 和 DFS 两种实现方式,如果仅仅要判断是否存在负环,DFS-SPFA 要比 BFS-SPFA 快上很多。但是在没有负环时要求出解,DFS-SPFA 会比 BFS-SPFA 慢很多。


    例题:洛谷P1993 小K的农场

    题意:求解差分约束系统,有 $m$ 条约束条件,每条都为形如 $x_a-x_bgeq c_k$,$x_a-x_bleq c_k$ 或 $x_a=x_b$ 的形式,判断该差分约束系统有没有解。

    $ egin{array}{|c|c|c|} hline ext{题意} & ext{转化} & ext{连边} \ hline x_a-x_bgeq c & x_b-x_aleq -c & add\_edge(a, b, -c) \ hline x_a-x_bleq c & x_a-x_bleq c & add\_edge(b, a, c) \ hline x_a=x_b & x_a-x_bleq 0,x_b-x_aleq 0 & add\_edge(a, b, 0),add\_edge(b, a, 0) \ hline end{array} $

    按表格描述连边建图后,跑 SPFA 判断是否存在负环即可。

    BFS-SPFA 开 O2 优化才能通过,否则 TLE :

    #include <cstdio>
    #include <cstring>
    #include <queue>
    using std::queue;
    const int INF = 0x3f3f3f3f;
    const int N = 10010;
    struct Edge
    {
        int to, nex, val;
    } edge[N<<1];
    int head[N], tot;
    bool inq[N];
    int dist[N], cnt[N];
    void init() {
        tot = 0;
        memset(head, 0, sizeof(head));
    }
    void add_edge(int u, int v, int w) {
        edge[++tot].nex = head[u];
        edge[tot].to = v;
        edge[tot].val = w;
        head[u] = tot;
    }
    bool bfs_spfa(int s, int n) {
        memset(inq, 0, sizeof(inq));
        memset(dist, 0x3f, sizeof(dist));
        queue<int> que;
        que.push(s);
        inq[s] = true;
        cnt[s] = 0;
        dist[s] = 0;
        while (!que.empty()) {
            int u = que.front();
            que.pop();
            inq[u] = false;
            for (int i = head[u]; i; i = edge[i].nex) {
                int v = edge[i].to;
                if (dist[v] > dist[u] + edge[i].val) {
                    cnt[v] = cnt[u] + 1;
                    if (cnt[v] > n) return false;
                    dist[v] = dist[u] + edge[i].val;
                    if (!inq[v]) {
                        inq[v] = true;
                        que.push(v);
                    }
                }
            }
        }
        return true;
    }
    
    int main() {
        int n, m;
        while (~scanf("%d %d", &n, &m)) {
            init();
            while (m--) {
                int op, u, v, w;
                scanf("%d %d %d", &op, &u, &v);
                if (op != 3) scanf("%d", &w);
                switch(op) {
                    case 1: add_edge(u, v, -w); break;
                    case 2: add_edge(v, u, w); break;
                    case 3: add_edge(u, v, 0); add_edge(v, u, 0);
                }
            }
            for (int i = 1; i <= n; i++) add_edge(0, i, 0);
            if (!bfs_spfa(0, n)) puts("No");
            else puts("Yes");
        }
        return 0;
    }
    View Code

    DFS-SPFA 只要几毫秒:

    #include <cstdio>
    #include <cstring>
    const int INF = 0x3f3f3f3f;
    const int N = 10010;
    struct Edge
    {
        int to, nex, val;
    } edge[N<<2];
    int head[N], tot;
    bool vis[N];
    int dist[N];
    void init() {
        tot = 0;
        memset(head, 0, sizeof(head));
        memset(vis, 0, sizeof(vis));
        memset(dist, 0x3f, sizeof(dist));
    }
    void add_edge(int u, int v, int w) {
        edge[++tot].nex = head[u];
        edge[tot].to = v;
        edge[tot].val = w;
        head[u] = tot;
    }
    bool dfs_spfa(int x) {
        vis[x] = true;
        for (int i = head[x]; i; i = edge[i].nex) {
            int y = edge[i].to, w = edge[i].val;
            if (dist[y] > dist[x] + w) {
                dist[y] = dist[x] + w;
                if (vis[y] || !dfs_spfa(y)) return false;
            }
        }
        vis[x] = false;
        return true;
    }
    
    int main() {
        int n, m;
        while (~scanf("%d %d", &n, &m)) {
            init();
            while (m--) {
                int op, u, v, w;
                scanf("%d %d %d", &op, &u, &v);
                if (op != 3) scanf("%d", &w);
                switch(op) {
                    case 1: add_edge(u, v, -w); break;
                    case 2: add_edge(v, u, w); break;
                    case 3: add_edge(u, v, 0); add_edge(v, u, 0);
                }
            }
            for (int i = 1; i <= n; i++) add_edge(0, i, 0);
            dist[0] = 0;
            if (!dfs_spfa(0)) puts("No");
            else puts("Yes");
        }
        return 0;
    }
    View Code

    参考资料:OI Wiki

  • 相关阅读:
    一生中常用工具
    Visual Studio2005 + Visual SourceSafe 2005 实现团队开发、
    如何正确处理SQL SERVER日志文件
    Oracle SQL精妙SQL语句讲解
    Asp.Net 备份和恢复SQL SERVER 数据库
    学习中
    oracle函数[单行字符串函数]
    个人博客大收集
    UML站点
    ASP.NET(c#)常用类函数
  • 原文地址:https://www.cnblogs.com/kangkang-/p/11585506.html
Copyright © 2020-2023  润新知