差分约束

前言

这个算是鸽了很久都没去学的算法。然后模拟赛考出来(我又不会并查集做法),所以在改题之前,还得先完成一下前置知识。

差分约束的前置知识

<Ⅰ>\(Spfa\)判断负环

【模板】负环

\(Spfa\)判断入队次数是否\(≥n\),如果是,说明有负环。感性理解一下,一个图上如果有负环,它会一直绕着负环跑,然后越来越小,越来越小……

\(code\):

#include<bits/stdc++.h>
using namespace std;
const int N=2e3+105,M=6e3+105;
int n,m,h[N],nex[M],to[M],v[M],cnt,d[N],tot[N],T,x,y,z;
bool f[N];//v已经有了 
inline void add(int x,int y,int z){
	to[++cnt]=y;v[cnt]=z;
	nex[cnt]=h[x];h[x]=cnt;
}
bool spfa(){
	memset(f,0,sizeof(f));memset(d,0x3f,sizeof(d));memset(tot,0,sizeof(tot));
	queue<int>q;
	d[1]=0;f[1]=1;q.push(1);
	while(!q.empty()){
		int x=q.front();q.pop();f[x]=0;
		for(int i=h[x];i;i=nex[i])
			if(d[to[i]]>d[x]+v[i]){
				d[to[i]]=d[x]+v[i];
				if(!f[to[i]]){
					if(++tot[to[i]]>n) return 1;
					q.push(to[i]);f[to[i]]=1;
				}
			}
	}
	return 0;
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z);if(z>=0)add(y,x,z);
		}
		spfa()?puts("YES"):puts("NO");
		memset(h,0,sizeof(h));cnt=0;
	}
	return 0;
}

<Ⅱ> 定义

差分约束系统

给出\(n\)变量\(x_1,x_2,x_3……x_n\)),和 \(m\)约束条件 (粗略理解为不等式,求一些满足约束条件的解。

假设给出一个这样的差分约束系统:
\(x_a-x_b ≤ k_1\)
\(x_a-x_c ≤ k_2\)
\(x_b-x_d ≤ k_3\)
\(x_d-x_c ≤ k_4\)

可以把它写成统一形式: \(x_i≤x_j+k\)

转化成图,大概是这样的:

如果要找\(x_a-x_d\)的最小值,即在图上跑点\(a\)到点\(d\)的最短路。

<Ⅲ> 例题

【模板】差分约束算法

\(code\):

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+105,M=6e4+105;
//变量名 
int n,m,h[N],nex[M],to[M],v[M],cnt,d[N],tot[N],T,x,y,z;
bool f[N];
//建图 ,to,cnt,nex,v,h
inline void add(int x,int y,int z){
	to[++cnt]=y;v[cnt]=z;
	nex[cnt]=h[x];h[x]=cnt;
} 
//判负环 ,f,d,tot
bool spfa(int s){
	memset(f,0,sizeof(f));memset(d,0x3f,sizeof(d));memset(tot,0,sizeof(tot));
	queue<int> q;
	d[s]=0;f[s]=1;q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();f[x]=0;
		for(int i=h[x];i;i=nex[i])
			if(d[to[i]]>d[x]+v[i]){
				d[to[i]]=d[x]+v[i];
				if(!f[to[i]]){
					if(++tot[to[i]]>n) return 1;//有负环->无解 
					q.push(to[i]);f[to[i]]=1;
				}
			}
	}
	return 0;
}	
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) add(0,i,0);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(y,x,z);//这里注意 
	}
	if(spfa(0))printf("NO");
	else for(int i=1;i<=n;i++)printf("%d ",d[i]);
	return 0;
} 

[FJOI2018]所罗门王的宝藏

题意

有一个 \(n×m\) 的矩阵,初始每个格子的权值都为 \(0\),可以对矩阵执行两种操作:

Ⅰ 、选择一行, 该行每个格子的权值加 \(1\) 或减 \(1\)

Ⅱ 、选择一列, 该列每个格子的权值加 \(1\) 或减 \(1\)

现在有 \(K\) 个限制,每个限制为一个三元组 \((x,y,c)\),代表格子 \((x,y)\) 权值等于 \(c\)。问是否存在一个操作序列,使得操作完后的矩阵满足所有的限制。如果存在输出 “\(Yes\)”,否则输出 “\(No\)”。

对于一个位置\((i,j)\),它的目标权值为\(c\),那么有关于第\(i\)行,第\(j\)列的值分别为\(x_i\) , \(y_j\)。可以列出一个式子:\(x_i + y_j = c\).

进一步转化:

\[\begin{cases} x_i+y_i\le c\\ x_i+y_i\ge c\\ \end{cases} \]

$ => $

\[\begin{cases} x_i-(-y_i)\le c\\ x_i-(-y_i)\ge c\\ \end{cases} \]

$ => $

\[\begin{cases} x_i\le c+(-y_i)\\ x_i-c\ge (-y_i)\\ \end{cases} \]

再设\(y_i' = (-y_i)\).

成功转换为差分约束问题

\(code\):

 #include<bits/stdc++.h>
using namespace std;
const int N=2e6+105,M=3e4+105;
//变量名 
int n,m,h[N],nex[M],to[M],v[M],cnt,d[N],tot[N],T,x,y,z,k;
bool f[N];
//建图 ,to,cnt,nex,v,h
inline void add(int x,int y,int z){
	to[++cnt]=y;v[cnt]=z;
	nex[cnt]=h[x];h[x]=cnt;
} 
//判负环 ,f,d,tot
bool spfa(int s){
	memset(f,0,sizeof(f));memset(d,0x3f,sizeof(d));memset(tot,0,sizeof(tot));
	queue<int> q;
	d[s]=0;f[s]=1;q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();f[x]=0;
		for(int i=h[x];i;i=nex[i])
			if(d[to[i]]>d[x]+v[i]){
				d[to[i]]=d[x]+v[i];
				if(!f[to[i]]){
					if(++tot[to[i]]>n+m) return 1;//有负环->无解 
					q.push(to[i]);f[to[i]]=1;
				}
			}
	}
	return 0;
}	
int main(){
	scanf("%d",&T);
	while(T--){
		memset(h,0,sizeof(h));memset(tot,0,sizeof(tot));memset(v,0,sizeof(v));
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=n+m;i++)add(0,i,0);
		for(int i=1;i<=k;i++){
			scanf("%d%d%d",&x,&y,&z);
			add(x,y+n,z);add(y+n,x,-z);
		}
		spfa(0)?printf("No\n"):printf("Yes\n");	
	}
	return 0;
} 
posted @ 2021-08-14 18:42  Nickle-NI  阅读(51)  评论(0编辑  收藏  举报