• 差分约束模板


    概念

    如果一个系统由 $n$ 个变量和 $m$ 个约束条件组成,其中每个约束条件形如 $x_i - x_j <= c_k$,其中 $c_k$ 是常数(可以是负数,也可以是非负数),则称其为差分约束系统。我们要解决的问题是:求一组解 $x_1= a-1, x_2 = a_2,...,x_n = a_n$,使得所有的约束条件得到满足,否则判断无解。

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

    注意到,如果 ${a_1,a_2,...a_n }$是该差分约束系统的一组解,那么对于任意的常数 $d$,${a_1+d, a_2+d, ..., a_n+d }$ 显然也是该差分约束系统的一组解。

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

    一般使用 $Belllman-Ford$ 或队列优化的 $Bellman-Ford$ (俗称 $SPFA$)判断图中是否存在负环,最坏的时间复杂度为 $O(nm)$.

    例题

    洛谷 P1993 小K的农场

    题意:

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

    分析:

    对于 $x_a = x_b$ 可以拆成等价的 $x_a leq x_b$ 和 $x_a geq x_b$.

    建图,然后跑单源最短路判断是否存在负环即可。

    Bellman-Ford版很慢,用时11.77s

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const ll INF = 1LL << 61;
    const int maxv = 4*10000 + 10;            //最大顶点数
    const int maxe = 4*10000 + 10;            //最大边数   //至少3倍空间
    ll dis[maxv];
    struct Edge
    {
        int u, v, w;
    }edge[maxe];
    int tot;
    int n, m;
    
    void AddEdge(int u, int v, int w)
    {
        edge[tot].u = u, edge[tot].v = v, edge[tot].w = w;
        tot++;
    }
    
    bool Bellman_Ford(int s)
    {
        //memset(dis, INF, sizeof(dis));
        for(int i = 0;i <= n;i++) dis[i] = INF;
        dis[s] = 0;
        for (int k = 1; k <= n - 1; k++)  //迭代n-1次
            for (int i = 0; i < tot; i++)  //检查每条边
            {
                int u = edge[i].u, v = edge[i].v, w = edge[i].w;
                if (dis[u] + w < dis[v])  dis[v] = dis[u] + w;
            }
    
        for (int i = 0; i < tot; i++)
            if (dis[edge[i].v] > dis[edge[i].u] + edge[i].w)
                return false;
        return true;
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 0;i < m;i++)
        {
            int order, a, b, c;
            scanf("%d", &order);
            if(order == 1)
            {
                scanf("%d%d%d", &a, &b, &c);
                AddEdge(a, b, -c);
            }
            else if(order == 2)
            {
                 scanf("%d%d%d", &a, &b, &c);
                 AddEdge(b, a, c);
            }
            else
            {
                scanf("%d%d", &a, &b);
                AddEdge(a, b, 0);
                AddEdge(b, a, 0);
            }
        }
        for(int i = 1;i <= n;i++)  AddEdge(0, i, 0);  //保证图的联通,权值随便
        n++;            //增加源点0
    
        if(Bellman_Ford(0)) printf("Yes
    ");
        else  printf("No
    ");
    
       // for(int i = 1;i < n;i++)  printf("%d: %d
    ", i, dis[i]);  可以输出一组解
    
        return 0;
    }
    View Code

    DFS-SPFA版的,很适合用来判断负环,0 ms(虽然时间复杂度极度不稳定)

    #include <cstdio>
    
    const int maxn = 1e4+4;
    const int inf = 0x7fffffff;
    int n, m;
    struct e{
        int to, nxt, w;
    }e[maxn<<3];
    int head[maxn], en;
    
    void add(int u, int v, int w){
        e[++en].to = v;
        e[en].w = w;
        e[en].nxt = head[u];
        head[u] = en;
    }
    
    int vis[maxn], dis[maxn], spfa_flag;
    void spfa(int u){
        if(spfa_flag) return;
        vis[u] = 1;
        for(int i = head[u]; i; i = e[i].nxt){
            int v = e[i].to;
            if(dis[v] > dis[u] + e[i].w){
                if(vis[v]){
                    spfa_flag = 1;
                    return;
                }
                dis[v] = dis[u] + e[i].w;
                spfa(v);
            }
        }
        vis[u] = 0;
    }
    
    int op, a, b, c;
    int main(){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++){
            scanf("%d", &op);
            if(op==3){
                scanf("%d%d", &a, &b);
                add(a, b, 0);
                add(b, a, 0);
            }
            if(op==1){
                scanf("%d%d%d", &a, &b, &c);
                add(a, b, -c);
            }
            if(op==2){
                scanf("%d%d%d", &a, &b, &c);
                add(b, a, c);
            }
        }
        //for(int i = 1; i <= n; i++) add(n+1, i, 0);
        for(int i = 1; i <= n; i++) dis[i] = 0;
        for(int i = 1; i <= n; i++) if (!vis[i]) spfa(i);
        if(spfa_flag) printf("No
    ");
        else printf("Yes
    ");
        return 0;
    }
    View Code

    参考链接:

    1. https://oi-wiki.org/graph/diff-constraints/#luogu-p1993-k

    2. https://www.luogu.org/record/9562703

  • 相关阅读:
    Quartz.net 定式调度任务
    Quartz.net 定时调度CronTrigger时间配置格式说明
    json 数据 添加 删除 排序
    Myeclipse 安装离线adt的方法 ()
    关于C# DataTable 的一些操作
    Android 设置代理(验证用户名和密码)
    js ShowDialogModal 关闭子页面并刷新父页面,保留查询条件
    sql语句中----删除表数据drop、truncate和delete的用法
    SQL数据库增删改查基本语句
    asp.net(C#)利用QRCode生成二维码---.NET菜鸟的成长之路
  • 原文地址:https://www.cnblogs.com/lfri/p/11335639.html
Copyright © 2020-2023  润新知