差分约束模板

概念

如果一个系统由 $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_b\geq 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\n");
    else  printf("No\n");

   // for(int i = 1;i < n;i++)  printf("%d: %d\n", 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\n");
    else printf("Yes\n");
    return 0;
}
View Code

 

 

 

参考链接:

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

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

转载于:https://www.cnblogs.com/lfri/p/11335639.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值