差分约束

前言:

可能是因为菜得没边吧,这点东西折腾了我快一上午的时间去学。大概写一下个人理解,可能会有一些问题。欢迎指出问题并补充遗漏哦~~

概念:(\(by:oiwiki\)

差分约束系统 是一种特殊的\(n\)元一次不等式组,它包含\(n\)个变量\(x_1,x_2,...,x_n\)以及\(m\)个约束条件,每个约束条件是由两个其中的变量作差构成的,形如\(x_i-x_j≤c_k\),其中\(1≤i,j≤n,1≤k≤m\)并且\(c_k\)是常数(可以是非负数,也可以是负数)。我们要解决的问题是:求一组解\(x_1=a_1,x_2=a_2,...x_n=a_n\),使得所有的约束条件得到满足,否则判断出无解。

每个约束条件\(x_i-x_j≤c_k\)都可以变成\(x_i≤x_j+c_k\),这与单源最短路中的三角形不等式(松弛)\(dis_y≤dis_x+val_i\)非常相似。因此,我们可以把每个变量\(x_i\)看做是图上的结点,对于每个约束条件\(x_i-x_j≤c_k\),从节点\(j\)指向节点\(i\)连一条长度为\(c_k\)的有向边。

个人理解:

差分约束就是一个将数论问题转化为图论问题的途径,它将变量转化为节点,将约束关系转化为有向边。关键词:不大于,不小于,大于,小于等。

实现过程:

首先从题目中找出约束关系并转化为上述形式(其实不转也行,就是可能会麻烦一点,容易乱),然后建图,最后跑一遍单源最短路(我看网上大部分都是\(spfa\),不过也有\(dij\),具体情况具体分析吧),找一下有没有环以此来判断有没有解。然后就没啦~~

例题:

\(First:\)P1993 小 K 的农场

思路:

先找约束关系,题中给了三个条件:

  1. \(x_a-x_b≥c_k\) .

  2. \(x_a-x_b≤c_k\) .

  3. \(x_a=x_b\) .

然后转化为统一形式:

  1. \(x_b-x_a≤-c_k\) .

  2. \(x_a-x_b≤c_k\) .

  3. \(x_a-x_b≤0\)\(x_a-x_b≥0\) .

接着进行建边:

  1. \(add(a,b,-c)\) .

  2. \(add(b,a,c)\) .

  3. \(add(a,b,0)\) , \(add(b,a,0)\) .

最后再跑一遍\(spfa\)判断是否有负环即可,若有负环则无解。

代码:

#include<iostream>
#include<queue>
#include<cstring>
#define int long long
using namespace std;
const int N=5e4+5;
int m,n,op,a,b,c,cnt,head[N],tot[N<<1],dis[N];bool vis[N<<1];queue<int> q;
struct node{int val,to,nxt;}e[N<<1];
inline void add(int x,int y,int z){
	e[++cnt].to=y;
	e[cnt].val=z;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}//建边 
signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>op;
		if(op==1){
			cin>>a>>b>>c;
			add(b,a,c);
		}else if(op==2){
			cin>>a>>b>>c;
			add(a,b,-c);
		}else if(op==3){
			cin>>a>>b;
			add(a,b,0);
			add(b,a,0);
		}
	}//约束关系 
	for(int i=1;i<=n;i++) add(0,i,0);
	memset(dis,-0x3f,sizeof(dis));
	dis[0]=0;vis[0]=1;q.push(0);
	while(!q.empty()){
		int now=q.front();q.pop();
		vis[now]=0;
		for(int i=head[now];i;i=e[i].nxt){
			if(dis[e[i].to]<dis[now]+e[i].val){
				dis[e[i].to]=dis[now]+e[i].val;
				if(!vis[e[i].to]){
					vis[e[i].to]=1;
					q.push(e[i].to);
					tot[e[i].to]++;
					if(tot[e[i].to]>=n){
						cout<<"No"<<'\n';
						return 0;
					}//有负环 无解 
				}
			}
		}
	}//spfa跑单源最短路 
	cout<<"Yes"<<'\n';//无负环 有解 
	return 0;//完结撒花~~
}

\(tips:\)这道题输入顺序与题目描述的顺序不一样,别被坑了(别问我怎么知道的www)。但是数据比较水,1,2写反了也有90的高分!!

\(Second:\)P4926 [1007] 倍杀测量者

前言吐槽:

这波女生赢麻了,既没有惩罚还能看乐子

思路:

先二分查找\(T\)的值,然后用差分约束判断是否有解。

二分方面不必多说。差分约束方面还是先找约束关系,不过这题不太一样的是这道题的约束关系是除法。此题有两种处理方式:不等号左右分别取一个\(log\),然后就可以跟普通差分约束一样做差了(详情见高中数学);另一种则是就按照除法的方式建边,不过是松弛的式子微微有些调整。我用的是后一种办法。

由题可得约束关系:

  1. \(\frac{x_a}{x_b}≥(k-T)\)

  2. \(\frac{x_b}{x_a}<(k+T)\)

统一形式为:

  1. \(x_a≥(k-T)x_b\)

  2. \(x_a>\frac{x_b}{k+T}\)

继续建边:

  1. \(add(b,a,k-T)\)

  2. \(add(b,a,\frac{1}{k+T})\)

然后再跑\(spfa\)判负环就行啦~~

代码:

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1024;
const double precision=1e-8;
int n,s,t,op[N],a[N],b[N],c[N],k[N],x[N],cnt,head[N],tot[N];bool vis[N];double l,r,dis[N];
struct node{int to,nxt;double val;}e[N<<1];
inline void add(int x,int y,double z){
	e[++cnt].to=y;
	e[cnt].val=z;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}//建边 
inline bool spfa(int now){
	queue<int> q;
	memset(vis,0,sizeof(vis));
	memset(dis,0,sizeof(dis));
	memset(tot,0,sizeof(tot));
	q.push(now);vis[now]=1;dis[now]=1;//因为是乘法,初始值赋为1 
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
			if(e[i].val*dis[x]>dis[y]){//不一样的松弛 
				dis[y]=e[i].val*dis[x];
				if(!vis[y]){
					vis[y]=1;
					q.push(y);
					tot[y]++;
					if(tot[y]>=n) return 1;//有环无解 
				}
			}
		}
		vis[x]=0;
	}
	return 0;//无环有解 
}
inline bool check(double T){
	cnt=1;
	memset(head,0,sizeof(head));
	for(int i=1;i<=s;i++){
		if(op[i]==1) add(b[i],a[i],k[i]-T);
		else add(b[i],a[i],1.0/(k[i]+T));
	}
	for(int i=1;i<=t;i++){
		add(0,c[i],x[i]);
		add(c[i],0,1.0/x[i]);
	}
	return spfa(0);
}//二分+建边 
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>s>>t;
	for(int i=1;i<=s;i++){
		cin>>op[i]>>a[i]>>b[i]>>k[i];
		r=max(r,(double)k[i]);
	}
	for(int i=1;i<=t;i++) cin>>c[i]>>x[i];
	while(r-l>precision){
		double mid=(l+r)/2;
		if(check(mid)) l=mid;//无解 
		else r=mid;//有解 
	}//二分 
	if(l>precision) printf("%lf",l);//判断精度输出 
	else cout<<-1;//无解 
	return 0;//完结撒花~~ 
}

\(tips:\) 注意边权,\(dis\)数组,\(l\)\(r\)\(mid\),还有精度的类型都是\(int\)。细心一点哦~~

update:2025年11月26日

模板题

$code$
#include<iostream>
#include<queue>
using namespace std;
const int N=5e3+5;
int n,m,x,y,z,cnt,head[N],dis[N],num[N];
queue<int> q;
bool vis[N];
struct flower{
	int to,nxt,val;
}e[N<<1];
inline void add(int x,int y,int z){
	e[++cnt].to=y;
	e[cnt].val=z;
	e[cnt].nxt=head[x];
	head[x]=cnt;
}
inline bool spfa(int s){
	for(int i=0;i<=n;i++) dis[i]=1e9,vis[i]=0;
	dis[s]=0;
	vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int x=q.front();q.pop();
		if(num[x]>n) return false;
		vis[x]=0;
		for(int i=head[x];i;i=e[i].nxt){
			int y=e[i].to;
			if(dis[y]>dis[x]+e[i].val){
				dis[y]=dis[x]+e[i].val;
				if(!vis[y]){
					num[y]++;
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	return true;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;i++) add(0,i,0);
	for(int i=1;i<=m;i++){
		cin>>x>>y>>z;
		add(y,x,z);
	}
	if(!spfa(0)) cout<<"NO"<<'\n';
	else for(int i=1;i<=n;i++) cout<<dis[i]<<' ';
	return 0;
}
posted @ 2025-08-24 15:40  晏清玖安  阅读(42)  评论(1)    收藏  举报