标签:typedef 注意 国家 超级 数组 amp 讲解 name 差分约束系统
差分约束是一个建立与最短路实现的算法,通常用来解决一些不等式组相关的问题。
其实这并不是一个新的算法,只是加入了新的思想罢了,使用范围比较小。
话不多说,直接进入正题吧。ヾ(?°?°?)??
其实差分约束系统就是一种特殊的 \(N\) 元不等式组,它包含 \(N\) 个变量 \([X_1,X_N]\) 以及 \(M\) 各约束条件。
每个约束条件都是两个变量的差构成的,形如 \(X_i-X_j\leq c_k\),其中 \(c_k\) 是一个可正可负的常数。
熟悉最短路的同学不难发现,当原式变形为 \(X_i\leq X_j+c_k\) 时,这与最短路问题中 \(dis_i\leq dis_j+w_{i,j}\) 十分相似。
尝试建立图论模型,那么显然,\(X_i\leq X_j+c_k\),就可以看成是:节点 \(j\) 与节点 \(i\) 之间有一条长度为 \(c_k\) 的有向边。
同时建立一个超级节点 \(0\),对于每一个节点 \(i\) 建立 \(0\to i\) 长度为 \(0\) 的有向边(\(1\leq i\leq N\))。
至此,求解的过程就变为求从 \(0\) 点出发的单源最短路径,解集就是 \(\{dis_1,dis_2,...,dis_N\}\)。
显然,当上式为解时,\(dis_1+d,dis_2+d,...,dis_N+d\) 也为一组合法的解集,因为差分时将 \(d\) 的同时减去了。
所以这种特殊的 \(N\) 元不等式的解并不唯一,因此做题时通常要求我们求最小解或任意一组解。
当然,这种不等式也是有可能无解的:当最短路出现负环时无解。
再普及一下:通常形如 \(dis_i\leq dis_j+w_{i,j}\) 的不等式被称为三角形不等式,其实知道也没什么用,但是当 dalao 讲题目时你要听得懂。
代码实现如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<queue>
#include<cstdlib>
#define N 200010
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n,m,head[N],tot=0,cnt[N],dis[N];
bool vis[N];
struct Edge{
int nxt,to,val;
}ed[N];
int read(){
int x=0,f=1;char c=getchar();
while(c<‘0‘ || c>‘9‘) f=(c==‘-‘)?-1:1,c=getchar();
while(c>=‘0‘ && c<=‘9‘) x=x*10+c-48,c=getchar();
return x*f;
}
void add(int u,int v,int w){
tot++;
ed[tot].nxt=head[u];
ed[tot].to=v;
ed[tot].val=w;
head[u]=tot;
return;
}
void SPFA(int s){
queue<int>q;
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
memset(cnt,0,sizeof(cnt));
vis[s]=true;dis[s]=0;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=false;
if(++cnt[u]>n){puts("NO");exit(0);}
for(int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to,w=ed[i].val;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v]) q.push(v);
}
}
}
return;
}
int main(){
n=read();m=read();
int u,v,w;
for(int i=1;i<=m;i++)
u=read(),v=read(),w=read(),add(v,u,w);
for(int i=1;i<=n;i++) add(0,i,0);
SPFA(0);
for(int i=1;i<=n;i++) printf("%d ",dis[i]);
return 0;
}
这个放在前面说是因为后面讲解例题时会用到。
一般做差分约束的题目时,大家往往苦于找到不等关系,但是通常并不会直接给你形如 \(X-Y\leq Z\) 的式子。
这是就需要一下这些技巧来帮助你确定不等关系了:
当然,我们不一定要强制不等式为 \(X-Y\leq Z\),同样可以用 \(X-Y\geq Z\)。
此时问题就变为求单源最长路,当有正环时无解。
前面提到过,差分约束的应用并不是很广泛,这里用几道例题来讲解。
在看例题之前,要提一句。众所周知,解决有负权边的最短路/最长路用的是 SPFA,所以不要给我用什么 Dijkscal。
设输入数据为 \(u,v,w\),\(s\) 为起点(即最小的 \(u-1\)),\(t\) 为终点(即最大的 \(v\)),\(dis_i\) 表示 \([s,i]\) 中选择了几个数。
那么对于每一组输入数据显然有 \(dis_v-dis_{u-1}\geq w\),于是可以在点 \(u-1\) 与点 \(v\) 中连接一条边权为 \(w\) 的边。
这里提出一个重要的道理说法:
在差分约束题目中,通常隐含着一些巧妙的不等关系,而这些因素往往容易忽略却至关重要。
首先,笔者要求读者时刻记住这句话。
然后来举个栗子解释一下这句话,在本题中就有以下几个非常容易忽略却至关重要的条件:
这都是很显然的规律,但是容易让初学萌新忽略而缺少条件关系,最终身败名裂。
问题:怎么注意到这些条件关系?
答:没什么固定的方法,因为每道题的关系都是不一样的,这需要选手有敏锐的观察能力和强大的逻辑关系。具体方法就是多做题。
代码如下:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#define N 50010
using namespace std;
int n, s = N + 1, t, head[N], dis[N], cnt = 0;
bool vis[N];
struct Edge {
int nxt, to, val;
} ed[3 * N];
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < ‘0‘ || c > ‘9‘) f = (c == ‘-‘) ? -1 : 1, c = getchar();
while (c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - 48, c = getchar();
return x * f;
}
void addedge(int x, int y, int z) {
cnt++;
ed[cnt].nxt = head[x];
ed[cnt].to = y;
ed[cnt].val = z;
head[x] = cnt;
return;
}
void SPFA() {
queue<int> q;
memset(dis, 0xcf, sizeof(dis));
memset(vis, false, sizeof(vis));
dis[s] = 0;
vis[s] = true;
q.push(s);
while (!q.empty()) {
int now = q.front();
q.pop();
vis[now] = false;
for (int i = head[now]; i; i = ed[i].nxt) {
int y = ed[i].to, z = ed[i].val;
if (dis[y] < dis[now] + z) {
dis[y] = dis[now] + z;
if (!vis[y])
q.push(y);
}
}
}
return;
}
int main() {
n = read();
int u, v, w;
for (int i = 1; i <= n; i++) {
u = read(), v = read(), w = read();
addedge(u - 1, v, w);
s = min(s, u - 1), t = max(t, v);
}
for (int i = s; i <= t; i++) {
addedge(i, i + 1, 0);
addedge(i + 1, i, -1);
}
SPFA();
printf("%d\n", dis[t]);
return 0;
}
当然这道题也可以用贪心+树状数组做,而且跑得比差分约束快。
具体方法由于限于篇幅(同时也因为这不是本章的重点),不做过多介绍,可以看看我的代码。
条件很多?不慌,一一分析即可。(记得用上面的技巧)
然后注意到每个小朋友都有糖,可以考虑建立超级节点:\(0\),然后就是 \(A-0\geq 1\)。
当然,这样比较耗时间(好像被 hack 了),所以有另一种方法:
既然每个小朋友至少有一个糖果,就令 \(dis_i=1(1\leq i\leq N)\),然后所有节点都入队即可。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <cstdlib>
#define N 1000010
using namespace std;
int n, k, dis[N], head[N], cnt = 0, t[N];
bool vis[N];
struct Edge {
int nxt, to, val;
} ed[N << 1];
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < ‘0‘ || c > ‘9‘) f = (c == ‘-‘) ? -1 : 1, c = getchar();
while (c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - 48, c = getchar();
return x * f;
}
void add(int u, int v, int w) {
cnt++;
ed[cnt].nxt = head[u];
ed[cnt].to = v;
ed[cnt].val = w;
head[u] = cnt;
return;
}
queue<int> q;
void SPFA() {
vis[0] = true;
dis[0] = 0;
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].val;
if (dis[v] < dis[u] + w) {
t[i]++;
if (t[i] > n - 1) {
printf("-1\n");
exit(0);
}
dis[v] = dis[u] + w;
if (!vis[v])
q.push(v);
}
}
}
return;
}
int main() {
n = read(), k = read();
int u, v, x;
for (int i = 1; i <= k; i++) {
x = read(), u = read(), v = read();
if (x == 1) {
add(v, u, 0);
add(u, v, 0);
} else if (x == 2)
add(u, v, 1);
else if (x == 3)
add(v, u, 0);
else if (x == 4)
add(v, u, 1);
else
add(u, v, 0);
if (x % 2 == 0 && u == v) {
printf("-1\n");
return 0;
}
}
for (int i = 1; i <= n; i++) {
vis[i] = dis[i] = 1;
q.push(i);
}
SPFA();
long long ans = 0;
for (int i = 1; i <= n; i++) ans += dis[i];
printf("%lld\n", ans);
return 0;
}
显然,简单的差分关系:
当然,此题是让我们求 \(max\{dis_N-dis_1\}\),但是由于差分约束的条件,还是应当求最短路。
从 \(1\) 开始有一个弊端,就是不一定能判断出无解,因为有些边不是直接与 \(1\) 相连。
那么为了判断连通性与负环,应当先建立节点 \(0\),并跑一遍 SPFA,然后再以 \(1\) 为源点跑一遍 SPFA。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <cstdlib>
#define N 200010
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n, m1, m2, head[N], tot = 0, cnt[N], dis[N];
bool vis[N];
struct Edge {
int nxt, to, val;
} ed[N];
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < ‘0‘ || c > ‘9‘) f = (c == ‘-‘) ? -1 : 1, c = getchar();
while (c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - 48, c = getchar();
return x * f;
}
void add(int u, int v, int w) {
tot++;
ed[tot].nxt = head[u];
ed[tot].to = v;
ed[tot].val = w;
head[u] = tot;
return;
}
void SPFA(int s) {
queue<int> q;
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
vis[s] = true;
dis[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
if (++cnt[u] > n) {
puts("-1");
exit(0);
}
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].val;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v])
q.push(v);
}
}
}
return;
}
int main() {
n = read();
m1 = read();
m2 = read();
int u, v, w;
for (int i = 1; i <= m1; i++) u = read(), v = read(), w = read(), add(u, v, w);
for (int i = 1; i <= m2; i++) u = read(), v = read(), w = read(), add(v, u, -w);
for (int i = 1; i <= n; i++) add(0, i, 0);
SPFA(0);
SPFA(1);
if (dis[n] == INF)
puts("-2");
else
printf("%d\n", dis[n]);
return 0;
}
总结一下:
然后...,就推荐大家看一下《数与图的完美结合——浅析差分约束系统 written by 华中师大一附中 冯威》。
的国家集训队论文。
完结撒花。
标签:typedef 注意 国家 超级 数组 amp 讲解 name 差分约束系统
原文地址:https://www.cnblogs.com/lpf-666/p/12689007.html