差分约束.

概念

  • 给你若干组约束条件,一般是 a 1 − a 2 ≤ k 1 , a 2 − a 3 ≤ k 2 . . . a_1-a_2\leq k_1,a_2-a_3\leq k_2... a1a2k1,a2a3k2...这种,让你判断这一组方程是否有解,或者求一组合法解

做法

  • 在最短路中,设有边u->v,边权为w,则有三角不等式 d i s [ v ] ≤ d i s [ u ] + w dis[v]\leq dis[u]+w dis[v]dis[u]+w,那么上述约束条件就可以转化为 a 1 ≤ a 2 + k 1 a_1\leq a_2+k_1 a1a2+k1,那么我们就连一条边2->1,边权为 k 1 k_1 k1,构造全部的不等式以后,全部进行这样的连边,这样我们跑最短路出来的就是答案,因为可能出现边权为负数,所以需要使用能够处理负边权的算法
  • 问题有两种问法,如果问最大值,那么我们要跑最短路;如果问最小值,那么我们要跑最长路,因为最短路右侧是 ≤ \leq ,所以求出来的是上界也就是最大值
  • 无解的判断就是如果出现环,那么就无解,因为出现环了也就是这些限制不能够同时满足
  • 因为spfa时间复杂度是 O ( n m ) O(nm) O(nm)的,所以这个范围内的题目都可以考虑如此解决

例题

https://www.luogu.com.cn/problem/P5960

  • 差分约束的模板题,建边之后跑spfa即可
  • 我们建立一个超级源点 0 0 0,这样可以避免出现图不连通的情况
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 5e3 + 100;
struct Edge{
  int next;
  int to;
  int val;  
}edge[N << 1];
int head[N];
int cnt;
void Add_Edge(int u, int v, int w){
  edge[cnt].next = head[u];
  edge[cnt].to = v;
  edge[cnt].val = w;
  head[u] = cnt++;
}
bool vis[N];
int dis[N];
int num[N];
bool spfa(int s, int n){
  queue<int> q;
  q.push(s);
  memset(dis, 0x3f, sizeof dis);
  dis[s] = 0;
  vis[s] = 1;
  num[s] = 1;
  while(!q.empty()){
    int u = q.front();
    q.pop();
    vis[u] = 0;
    for(int i=head[u];~i;i=edge[i].next){
      int v = edge[i].to;
      if(dis[v] > dis[u] + edge[i].val){
        dis[v] = dis[u] + edge[i].val;
        num[v] += 1;
        if(num[v] >= n + 1){
          return false;
        }
        if(!vis[v]){
          q.push(v);
          vis[v] = 1;
        }
      }
    }
  }
  for(int i=1;i<=n;i++){
    cout << dis[i] << " \n"[i == n];
  }
  return true;
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  memset(head, -1, sizeof head);
  for(int i=0;i<m;i++){
    int u, v, w;
    cin >> u >> v >> w;// dis[u] - dis[v] <= w <=> dis[u] <= dis[v] + w;
    Add_Edge(v, u, w);
  }
  for(int i=1;i<=n;i++){
    Add_Edge(0, i, 0);
  }
  if(!spfa(0, n)) cout << "NO\n";
  return 0;
}

https://www.luogu.com.cn/problem/P1993

  • 如果是一样多就相当于 d i s [ u ] ≤ d i s [ v ] dis[u]\leq dis[v] dis[u]dis[v] d i s [ u ] ≥ d i s [ v ] dis[u]\geq dis[v] dis[u]dis[v],其余和上面相同

https://www.luogu.com.cn/problem/P3275

  • 如果用差分约束来做这个题,这里面有六个约束,第六个约束是 d i s [ u ] ≥ 1 dis[u]\geq 1 dis[u]1,这可以通过设置超级源点到其他点的距离均为1来解决,剩下的我们就按照上述建图方式即可,因为问的是至少需要准备的糖果数量,所以我们要跑最长路,同时也能使得边权均 ≥ 0 \geq0 0,这样也保证了求值的合法性

https://www.luogu.com.cn/problem/P1250

  • 也可使用差分约束

http://poj.org/problem?id=3169

  • 一样的思路,注意约束不要漏了,附个代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <stack>
#include <queue>
#include <iomanip>
#include <cstdio>
#include <set>
#include <map>
#include <cmath>

using namespace std;

typedef long long ll;

const int N = 10005;

struct Edge{
  int next;
  int to;
  int val;
}edge[3 * N];
int cnt = 0;
int head[N];
void Add_Edge(int u, int v, int w){
  edge[cnt].next = head[u];
  edge[cnt].to = v;
  edge[cnt].val = w;
  head[u] = cnt++;
}
int vis[N];
int dis[N];
int num[N];
const int INF = 0x3f3f3f3f;
int spfa(int s, int n){
  queue<int> q;
  q.push(s);
  vis[s] = 1;
  num[s] = 1;
  memset(dis, 0x3f, sizeof dis);
  dis[s] = 0;
  while(!q.empty()){
    int u = q.front();
    q.pop();
    vis[u] = 0;
    for(int i=head[u];~i;i=edge[i].next){
      int v = edge[i].to;
      if(dis[v] > dis[u] + edge[i].val){
        dis[v] = dis[u] + edge[i].val;
        num[v] += 1;
        if(num[v] >= n){
          return -1;
        }
        if(!vis[v]){
          q.push(v);
          vis[v] = 1;
        }
      }
    }
  }
  if(dis[n] == INF) return -2;
  return dis[n];
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, ML, MD;
  cin >> n >> ML >> MD;
  memset(head, -1, sizeof head);
  for(int i=0;i<ML;i++){// 统一小的指向大的
    int u, v, w;
    cin >> u >> v >> w;
    if(u > v) swap(u, v);// dis[v] - dis[u] <= w <=> dis[v] <= dis[u] + w <=> [u, v] = w
    Add_Edge(u, v, w);
  }
  for(int i=0;i<MD;i++){
    int u, v, w;
    cin >> u >> v >> w;
    if(u > v) swap(u, v);// dis[v] - dis[u] >= w <=> dis[u] <= dis[v] - w <=> [v, u] = -w
    Add_Edge(v, u, -w);
  }
  for(int i=1;i<n;i++){
    Add_Edge(i + 1, i, 0); // dis[i] <= dis[i + 1]
  }
  cout << spfa(1, n) << '\n';
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值