概念
- 给你若干组约束条件,一般是 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... a1−a2≤k1,a2−a3≤k2...这种,让你判断这一组方程是否有解,或者求一组合法解
做法
- 在最短路中,设有边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 a1≤a2+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;
}