【学习笔记】(1) 差分约束
1.算法介绍
差分约束系统 是一种特殊的
差分约束系统中的每个约束条件
注意到,如果
因为一般这个
模板题 P5960 【模板】差分约束算法。
#include<bits/stdc++.h> #define N 5005 using namespace std; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, tot; int to[N], Head[N], Next[N], edge[N]; bool vis[N]; int dis[N], in[N]; queue<int> q; void add(int u, int v, int w){ to[++tot] = v, Next[tot] = Head[u] ,Head[u] = tot, edge[tot] = w; } int main(){ n = read(), m = read(); for(int i = 1; i <= m; ++i){ int v = read(), u = read(), w = read(); add(u, v, w); } for(int i = 1; i <= n; ++i) q.push(i), vis[i] = 1, ++in[i]; //这里也可以建一个超级源点 while(!q.empty()){ int x = q.front(); q.pop(); vis[x] = 0; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(dis[y] > dis[x] + edge[i]){ dis[y] = dis[x] + edge[i]; if(!vis[y]) ++in[y], vis[y] = 1, q.push(y); if(in[y] > n) return printf("NO\n"), 0; } } } for(int i = 1; i <= n; ++i) printf("%d ",dis[i]); printf("\n"); return 0; }
2.解的字典序极值
一般而言差分约束系统的解没有 “字典序” 这个概念,因为我们只对变量之间的差值进行约束,而变量本身的值可以随着某个变量取值的固定而固定,所以解的字典序可以趋于无穷大或无穷小。
字典序的极值建立于变量有界的基础上,假如
首先明确一点,对于一条从
那么通过 SPFA 求得的一组解,恰为字典序最大解。
证明:
考虑 0 到每个节点的最短路树。对于树上每条边均满足
这说明树上的
证毕
对于字典序最小解,我们限制
3.例题
3.1 P5590 赛车游戏
好题。
我们可以转化一下思路,与其设置边权使路径长度相等,不如设置路径长度去拟合边权的限制。
设
为
#include<bits/stdc++.h> #define N 1005 #define M 4005 using namespace std; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, tot; int Head[N], to[M], Next[M], edge[M]; int dis[N], in[N]; bool vis[N], VIS[2][N], flag[N]; vector<int> E[2][N]; struct edge{ int u, v; }a[M]; void add(int u, int v, int w){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w; } void dfs(int x, int k){ VIS[k][x] = 1; for(auto y : E[k][x]){ if(VIS[k][y]) continue; dfs(y, k); } } bool spfa(int s){ memset(dis, 0x3f, sizeof(dis)); queue<int> q; q.push(s); vis[s] = 1, dis[s] = 0, ++in[s]; while(!q.empty()){ int x = q.front(); q.pop(), vis[x] = 0; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(dis[y] > dis[x] + edge[i]){ dis[y] = dis[x] + edge[i]; if(!vis[y]) ++in[y], vis[y] = 1, q.push(y); if(in[y] > n) return true; } } } return false; } int main(){ srand(time(0)); n = read(), m = read(); for(int i = 1; i <= m; ++i){ a[i].u = read(), a[i].v = read(); E[0][a[i].u].push_back(a[i].v), E[1][a[i].v].push_back(a[i].u); } dfs(1, 0), dfs(n, 1); if(!VIS[0][n]) return printf("-1\n"), 0; for(int i = 1; i <= n; ++i) flag[i] = (VIS[0][i] & VIS[1][i]); for(int i = 1; i <= m; ++i){ if(flag[a[i].u] && flag[a[i].v]) add(a[i].u, a[i].v, 9), add(a[i].v, a[i].u, -1); } if(spfa(1)) return printf("-1\n"), 0; printf("%d %d\n", n, m); for(int i = 1; i <= m; ++i){ if(flag[a[i].u] && flag[a[i].v]) printf("%d %d %d\n", a[i].u, a[i].v, dis[a[i].v] - dis[a[i].u]); else printf("%d %d %d\n", a[i].u, a[i].v, rand() % 9 + 1); } return 0; }
3.2 CF241E Flights
基本同上题,把边权限制改一下在进行差分约束就行了。
#include<bits/stdc++.h> #define N 1005 #define M 10005 using namespace std; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, tot; int Head[N], to[M], Next[M], edge[M]; int dis[N], in[N]; bool vis[N], VIS[2][N], flag[N]; vector<int> E[2][N]; struct edge{ int u, v; }a[M]; void add(int u, int v, int w){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w; } void dfs(int x, int k){ VIS[k][x] = 1; for(auto y : E[k][x]){ if(VIS[k][y]) continue; dfs(y, k); } } bool spfa(int s){ memset(dis, 0x3f, sizeof(dis)); queue<int> q; q.push(s); vis[s] = 1, dis[s] = 0, ++in[s]; while(!q.empty()){ int x = q.front(); q.pop(), vis[x] = 0; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(dis[y] > dis[x] + edge[i]){ dis[y] = dis[x] + edge[i]; if(!vis[y]) ++in[y], vis[y] = 1, q.push(y); if(in[y] > n) return true; } } } return false; } int main(){ srand(time(0)); n = read(), m = read(); for(int i = 1; i <= m; ++i){ a[i].u = read(), a[i].v = read(); E[0][a[i].u].push_back(a[i].v), E[1][a[i].v].push_back(a[i].u); } dfs(1, 0), dfs(n, 1); for(int i = 1; i <= n; ++i) flag[i] = (VIS[0][i] & VIS[1][i]); for(int i = 1; i <= m; ++i){ if(flag[a[i].u] && flag[a[i].v]) add(a[i].u, a[i].v, 2), add(a[i].v, a[i].u, -1); } if(spfa(1)) return printf("No\n"), 0; printf("Yes\n"); for(int i = 1; i <= m; ++i){ if(flag[a[i].u] && flag[a[i].v]) printf("%d\n", dis[a[i].v] - dis[a[i].u]); else printf("%d\n", rand() % 2 + 1); } return 0; }
3.3 P3275 [SCOI2011]糖果
这题可以将题中五个关系改成差分约束的限制。
- 如果
, 表示第 个小朋友分到的糖果必须和第 个小朋友分到的糖果一样多,改为 和 ; - 如果
, 表示第 个小朋友分到的糖果必须少于第 个小朋友分到的糖果,改为 ; - 如果
, 表示第 个小朋友分到的糖果必须不少于第 个小朋友分到的糖果,改为 ; - 如果
, 表示第 个小朋友分到的糖果必须多于第 个小朋友分到的糖果,改为 ; - 如果
, 表示第 个小朋友分到的糖果必须不多于第 个小朋友分到的糖果,改为 ;
至于为什么这么改,因为题中要求的是满足限制的最少糖果数且
还有这题由于数据范围比较大,我们最好不使用 SPFA ,以防被卡。我们可以使用 Tarjan 缩点来判正环,有正环就无解,具体地,对于在同一强连通分量的点来说,如果他们之间的边为 1 ,那么肯定有正环,因为边权要么为 1 ,要么为 0 。 之后由于 Tarjan 缩点后 成了 DAG 图,我们可以用拓扑来求解答案,对于同一强连通分量的点他们得到的糖果数显然是相同的。
#include<bits/stdc++.h> #define M 300005 #define N 100005 #define ll long long using namespace std; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, tot, top, t, cnt; ll ans = 0; int to[M], Next[M], Head[N], edge[M], fr[M]; int f[N], in[N]; bool vis[N]; int dfn[N], low[N], s[N], col[N], sz[N]; void add(int u, int v, int w){ to[++tot] = v, fr[tot] = u, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w; } void tarjan(int x){ dfn[x] = low[x] = ++t, s[++top] = x, vis[x] = 1; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]); else if(vis[y]) low[x] = min(low[x], dfn[y]); } if(dfn[x] == low[x]){ int k = -1; ++cnt; while(k != x){ k = s[top--], ++sz[cnt], vis[k] = 0, col[k] = cnt; } } } int main(){ // freopen("1.in","r",stdin); n = read(), m = read(); for(int i = 1; i <= m; ++i){ int opt = read(), x = read(), y = read(); if(opt == 1) add(x, y, 0), add(y, x, 0); else if(opt == 2) add(x, y, 1); else if(opt == 3) add(y, x, 0); else if(opt == 4) add(y, x, 1); else add(x, y, 0); } for(int i = 1; i <= n; ++i) add(0, i, 1); for(int i = 0; i <= n; ++i) if(!dfn[i]) tarjan(i); m = tot; memset(Head, 0, sizeof(Head)); tot = 0; for(int i = 1; i <= m; ++i){ int x = col[fr[i]], y = col[to[i]]; if(x == y && edge[i]) return printf("-1\n"), 0; if(x != y) add(x, y, edge[i]), ++in[y]; } queue<int> q; for(int i = 1; i <= cnt; ++i) if(!in[i]) q.push(i); while(!q.empty()){ int x = q.front(); q.pop(); for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; --in[y]; f[y] = max(f[y], f[x] + edge[i]); if(!in[y]) q.push(y); } } for(int i = 1; i <= cnt; ++i) ans += 1ll * f[i] * sz[i]; printf("%lld\n", ans); return 0; }
3.4 P3530 [POI2012]FES-Festival
将关系转为差分约束的限制,发现是稠密图,考虑使用 Floyd ,
还是要 Tarjan 缩点,然后考虑无解的情况:
-
如果是负环,那么肯定无解,判定条件 :
。 -
零环显然可以
-
如果对于正环,可以发现如果全是 1 的话 才是无解,因为在建
的边时,有正反边权,那么 全是 1 的正环 其实也是全是 -1 的负环,如果环中没有为 0 的边,其实如果任意方向权值和不为0,一定无解,因为反一下就有正负环,而有 0 边 的话,可以限制方向,如果与大多数的 边权为1 的边相同的话,那么就有解,因为无法反转了,被 0 这条单向边限定死了,否则无解。
其实说了那么多,其实就只要判负环就可以了。
那最后的答案呢?其实就是各个强连通分量内的最长路之和 + 强连通分量数之和。为什么可以这样呢?因为各个强连通分量如果有连接的话,必然是 0 边,因为 1 和 -1 是配套,方向相反,那如果不是 0 边,其实两个强连通分量是在同一个强连通分量内的,不符合。那这样两个强连通分量之间就可以无限拉长,就可以分开来考虑。按照差分约束算法,从一个点
证明:
强连通分量两两之间最短路的最大值就代表
画一下图可以发现,这样的
证毕。
后记:一直以为自己懂了,直到写题解时才发现自己可能没有真的理解,这道题还有好多值得探究的问题,有些我可能讲不明白,讲起来还可能有点繁琐,没有图不是很直观,而且我也不想画了,那就这样吧,至少我知道了(bushi)。
#include<bits/stdc++.h> #define N 605 #define M 200005 #define INF 0x3f3f3f3f using namespace std; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m1, m2, tot, t, top, scc, sum; int Head[N], Next[M], to[M], edge[M]; int low[N], dfn[N], s[N], col[N], sz[N]; bool vis[N]; int dis[N][N], ans[N]; void add(int u, int v, int w){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w; } void tarjan(int x){ dfn[x] = low[x] = ++t, vis[x] = 1, s[++top] = x; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]); else if(vis[y]) low[x] = min(low[x], dfn[y]); } if(low[x] == dfn[x]){ int k = -1; ++scc; while(k != x){ k = s[top--], vis[k] = 0, ++sz[scc], col[k] = scc; } } } int main(){ n = read(), m1 = read(), m2 = read(); memset(dis, 0x3f, sizeof(dis)); for(int i = 1; i <= m1; ++i){ int u = read(), v = read(); dis[u][v] = 1, dis[v][u] = -1; add(u, v, 1), add(v, u, -1); } for(int i = 1; i <= m2; ++i){ int u = read(), v = read(); if(dis[v][u] == INF) dis[v][u] = 0; add(v, u, 0); } for(int i = 1; i <= n; ++i){ dis[i][i] = 0; if(!dfn[i]) tarjan(i); } for(int k = 1; k <= n; ++k){ for(int i = 1; i <= n; ++i){ if(col[i] != col[k] || dis[i][k] == INF) continue; for(int j = 1; j <= n; ++j){ if(col[i] != col[j]) continue; dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); } } } for(int i = 1; i <= n; ++i){ if(dis[i][i] < 0) return printf("NIE\n"), 0; for(int j = 1; j <= n; ++j){ if(col[i] == col[j]) ans[col[i]] = max(ans[col[i]], dis[i][j] + 1); } } for(int i = 1; i <= scc; ++i) sum += ans[i]; printf("%d\n", sum); return 0; }
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17417612.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步