差分约束

差分约束

讲解

如果一个不等式组由 \(n\) 个变量和 \(m\) 个约束条件组成,形成 \(m\) 个形如\(x[ j ]-x[ i ]≤k\)\(i,j∈[1,n]\)\(k\) 为常数)的不等式,则称其为差分约束系统。换句话说,差分约束系统就是求解一组变量的不等式组的算法。

\(d_v-d_u<=w\) 和 spfa 中的松弛操作 \(d_v<=d_u+w\) 非常像
只要没有负环则能得到⼀组合法的{\(d_i\)} ,则原问题有解且 {\(d_i\)} 就是⼀组可行解

做题步骤

1.判断是让求至多(最短路)还是至少(最长路)

2.按照最短/长路松弛操作,对应题目约束条件连边

3.看题目中一些特殊要求,继续完善连边

4.建立超级源点0,向每个点连边权为 0 的边

最后的答案输出:

一般是让输出 dis[n] , dis[1~n], 无解:最短路有负环,最长路有正环(用cnt[ ]判断每个点取出次数是不是≥n )

spfa

k;相当于限制\(x[ i ]\)的最大/小值

连边后求最短路
\(x[ i ]−x[ j ]≤k\) 变形为 \(x[ i ]≤k+x[ j ]\),即从 \(j\)\(i\) 连一条边权为 \(k\) 的边。加入超级源点后求最短路,得到\(x[i] ≤ k\) 所有 \(x\) 最大解。

连边后求最长路
\(x[ i ]−x[ j ]≥k\) 变形为 \(x[ j ]≤x[ i ]-k\),即从 \(i\)\(j\) 连一条边权为 \(-k\) 的边。加入超级源点后求最长路,得到\(x[i]≥k\) 所有 \(x\) 最小解。

判负环spfa必然退化为O(nm)

其他未写的题目:

附上一篇有超详细解析和题目的blog

模板

一定注意建超级源点

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=5005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int n,m;
int hd[N],to[N<<1],tot,nxt[N<<1],w[N<<1];
inline void add(int x,int y,int z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;	
}
queue<int>q;
int dis[N],cnt[N];
bool vis[N];
bool spfa() {
	q.push(0);
	dis[0]=0;
	cnt[0]=1;
	while(q.size()) {
		int x=q.front();q.pop();
		vis[x]=0;
		for(int i=hd[x];i;i=nxt[i]) {
			int y=to[i];
			if(dis[y]>dis[x]+w[i]) {
				dis[y]=dis[x]+w[i];
				if(++cnt[y]>n) return 1;
				if(!vis[y]) vis[y]=1,q.push(y);
			}
		}
	}
	return 0;
}
int main() {
	memset(dis,0x3f,sizeof(dis));
	n=read();m=read();
	for(int i=1;i<=m;i++) {
		int u=read(),v=read(),w=read();
		add(v,u,w);
	}
	for(int i=1;i<=n;i++)
		add(0,i,0);
	if(spfa()) puts("NO");
	else {
		for(int i=1;i<=n;i++)
			printf("%d ",dis[i]);
	}
	return 0;
}

种树

求最少种树量——最长路

\(dis[y]<dis[x-1]+w[i]\) —— 区间 \([x,y]\)至少种 \(w[i]\) 棵树

for(int i=0;i<=n;i++){
	if(i!=0)add(i-1,i,0),dis[i]=-inf; // [i-1,i]最少种0棵
	if(i!=n)add(i,i-1,-1);//[i-1,i]最多种1棵
}
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=50005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
const int inf=0x3f3f3f3f;
int n,m;
int hd[N],to[N<<1],tot,nxt[N<<1],w[N<<1];
inline void add(int x,int y,int z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;	
}
queue<int>q;
int dis[N],cnt[N];
bool vis[N];
bool spfa() {
	q.push(0);
	dis[0]=0;
	cnt[0]=1;
	while(q.size()) {
		int x=q.front();q.pop();
		vis[x]=0;
		for(int i=hd[x];i;i=nxt[i]) {
			int y=to[i];
			if(dis[y]<dis[x]+w[i]) {
				dis[y]=dis[x]+w[i];
				if(++cnt[y]>n) return 1;
				if(!vis[y]) vis[y]=1,q.push(y);
			}
		}
	}
	return 0;
}
int main() {
	n=read();m=read();
	for(int i=1;i<=m;i++) {
		int u=read(),v=read(),w=read();
		add(u-1,v,w);
	}
	for(int i=0;i<=n;i++) {
		if(i) add(i-1,i,0),dis[i]=-inf;
		if(i!=n) add(i,i-1,-1);
	}
	if(spfa()) puts("NO");
	else printf("%d ",dis[n]);
	return 0;
}

也可用贪心和线段树优化

POJ 3169 Layout

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

注意限制 i+1 ---> i 连 0 (牛可以在同一个位置)

从0开始跑spfa判断图是不是联通的——洛谷毒瘤hack

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
const int N=2005;
const int M=1000005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int n,m1,m2;
int hd[N],to[M],tot,nxt[M],w[M],cnt[N];
inline void add(int x,int y,int z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
bool vis[N];
long long dis[N];
bool spfa(int x) {
	queue<int>q;
	for(int i=0;i<=n;i++) dis[i]=0x3f3f3f3f,vis[i]=0;
	q.push(x);dis[x]=0;
	while(!q.empty()) {
		int x=q.front();q.pop();
		vis[x]=0;
		for(int i=hd[x];i;i=nxt[i]) {
			int y=to[i];
			if(dis[y]>dis[x]+w[i]) {
				dis[y]=dis[x]+w[i];
				if(!vis[y]) {
					vis[y]=1;q.push(y);
					if(++cnt[y]==n) {
						puts("-1");return 1;
					}
				}				
			}
		}
	}
	return 0;
}
int main() {
	n=read();m1=read();m2=read();
	int a,b,c;
	for(int i=1;i<=m1;i++) {
		a=read();b=read();c=read();
		add(a,b,c);
	}
	for(int i=1;i<=m2;i++) {
		a=read();b=read();c=read();
		add(b,a,-c);
	}
	for(int i=1;i<=n;i++)
		add(0,i,0);
	for(int i=1;i<n;i++)
		add(i+1,i,0);
	if(spfa(0)) return 0;
	spfa(1);
	printf("%lld\n",dis[n]==0x3f3f3f3f?-2:dis[n]);
	return 0;
}

小 K 的农场

  • 农场a比农场b至少多种植了c个单位的作物, \(dis[a]-dis[b]>c\) , \(dis[b]<dis[a]-c\) ; \(a\)\(b\)连一条 \(-c\)
  • 农场a比农场b至多多种植了c个单位的作物,\(dis[a]-dis[b]<c\) , \(dis[a]<dis[b]+c\) ; \(b\)\(a\)连一条 \(c\)
  • 农场\(a\)与农场\(b\)种植的作物数一样多。\(a-->b\)连一条\(0\) \(b-->a\)连一条\(0\)

然后快乐AC

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=100005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
const int inf=0x3f3f3f3f;
int n,m;
int hd[N],to[N<<1],tot,nxt[N<<1],w[N<<1];
inline void add(int x,int y,int z) {
	to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;	
}
queue<int>q;
int dis[N],cnt[N];
bool vis[N];
bool spfa() {
	memset(dis,0x3f,sizeof(dis));
	q.push(0);
	dis[0]=0;
	cnt[0]=1;
	while(q.size()) {
		int x=q.front();q.pop();
		vis[x]=0;
		for(int i=hd[x];i;i=nxt[i]) {
			int y=to[i];
			if(dis[y]>dis[x]+w[i]) {
				dis[y]=dis[x]+w[i];
				if(++cnt[y]>n) return 1;
				if(!vis[y]) vis[y]=1,q.push(y);
			}
		}
	}
	return 0;
}
int main() {
	n=read();m=read();
	for(int i=1;i<=m;i++) {
		int op=read(),u=read(),v=read(),ww;
		if(op==1) ww=read(),add(u,v,-ww);
		else if(op==2) ww=read(),add(v,u,ww);
		else add(u,v,0),add(v,u,0);
	}
	for(int i=1;i<=n;i++) add(0,i,0);
	puts(spfa()?"No":"Yes");
	return 0;
}

糖果

这题卡邻接表???vector 可过

好像正解是tarjan —— this

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<utility>
using namespace std;
#define N 300005
#define inf 0x3f3f3f3f
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x;
}
vector < pair<int,int> > g[N];
queue < int > q;
int n,m;
int dis[N],cnt[N];
bool vis[N],f;
long long ans;
int main(){
	n=read();m=read();
	for(int i=1,op,x,y;i<=m;i++){
		op=read();x=read();y=read();
		if(op==1) g[x].push_back(make_pair(y,0)),g[y].push_back(make_pair(x,0));
		else if(op==2) g[x].push_back(make_pair(y,1));
		else if(op==3) g[y].push_back(make_pair(x,0));
		else if(op==4) g[y].push_back(make_pair(x,1));
		else g[x].push_back(make_pair(y,0));
		if(x==y&&(op==2||op==4)){puts("-1");return 0;}
	}
	for(int i=1;i<=n;i++)
		g[0].push_back(make_pair(i,1));
	memset(dis,-0x3f,sizeof(dis));
	q.push(0);vis[0]=1;dis[0]=0;
	while(!q.empty()){
		int x=q.front();q.pop();
		vis[x]=0;
		for(int i=0,siz=g[x].size();i<siz;i++){
			int y=g[x][i].first,w=g[x][i].second;
			if(dis[y]<dis[x]+w){
				dis[y]=dis[x]+w;
				if(++cnt[y]==n){ puts("-1"); return 0;}
				if(!vis[y]){
					vis[y]=1;q.push(y);
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
		ans+=dis[i];
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-09-01 16:32  ke_xin  阅读(35)  评论(0编辑  收藏  举报