差分约束

差分约束系统是什么?

差分约束系统指的是一个序列 \(x={x_1,x_2,\cdots x_m}\) 和以如下形式出现的 \(n\) 元一次不等式组。

\[\left\{\begin{matrix} x_{i_1}-x_{j_1}\le c_{k_1} \\x_{i_2}-x_{j_2}\le c_{k_2} \\\vdots \\x_{i_n}-x_{j_n}\le c_{k_n} \end{matrix}\right. \]

下面给出几道例题。


还是说说前置知识。

P3385 【模板】负环

Description

给你一个 \(n\) 个点的有向图,让你判断图中是否有能从 \(1\) 号点出发可以到达的负环。

负环定义为一条边权和为负的回路。

Solution

思考负环的一些性质。

容易发现负环上一点与任意一点的最短路都不存在,因为你可以跑无数遍负环,边权和会越来越大。我们需要做的是找出一个限度值,如果超过了这个限度值就知道出现了负环。

注意到跑完一次单源最短路最多也就是把所有节点全部入队更新。如果点数为 \(n\),同一个节点最多只可能入队 \(n-1\) 次。

所以,开一个数组 cnt 记录每个节点入队的次数,如果次数超过 \(n-1\) 就说明有负环。

SPFA 在卡满情况下与朴素 Bellman-Ford 复杂度都为 \(O(nm)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long T,n,m,tot,head[2005],dis[2005],cnt[2005];
bool vis[2005];
queue<int>q;
struct node{
	int from,to,w,nxt;
}e[6005];
inline void add_edge(int u,int v,int w){
	e[++tot].from=u;
	e[tot].to=v;
	e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}
inline bool SPFA(int s){
	memset(dis,63,sizeof(dis));
	dis[s]=0;
	vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to,w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					cnt[v]++;
					if(cnt[v]>=n){
						return 1;
					}
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return 0;
}
signed main(){
	cin>>T;
	while(T--){
		tot=0;
		memset(head,0,sizeof(head));
		memset(e,0,sizeof(e));
		memset(vis,0,sizeof(vis));
		memset(cnt,0,sizeof(cnt));
		while(!q.empty()){
			q.pop();
		}
		cin>>n>>m;
		for(int i=1;i<=m;i++){
			int u,v,w;
			cin>>u>>v>>w;
			if(w>=0){
				add_edge(u,v,w);
				add_edge(v,u,w);
			}
			else{
				add_edge(u,v,w);
			}
		}
		if(SPFA(1)){
			cout<<"YES"<<endl;
		}
		else{
			cout<<"NO"<<endl;
		}
	}
	return 0;
}

P5960 【模板】差分约束

Description

给你一个差分约束系统,让你判断这个差分约束系统是否存在合法的解,如果存在,输出任意一组可行解。

\(1\le n,m \le 5\times 10^3\)

Solution

我们单独把一个式子拿出来看:

\[x_i-x_j\le c_k \]

移项:

\[x_i\le x_j+c_k \]

瞬间想起了一些不好的东西。

int v=e[i].to,w=e[i].w;
if(dis[v]>=dis[u]+w){
	dis[v]=dis[u]+w;
}

对没错这个东西很像最短路中的三角形不等式\(\text{dis[v]}\le \text{dis[u]+w}\)

考虑把三角形不等式稍作改动:\(\text{dis[i]}\le \text{dis[j]+k}\)。最终的最短路径中每条边都要满足这个不等式,而差分约束的最终可行解每个 \(x_i\) 也都要满足这个不等式。

因此,对于每一个不等式,我们考虑建一条从 \(j\)\(i\),长度为 \(c_k\) 的有向边。

我们先建立一个超级源点,不妨设为 \(0\) 号点。从 \(0\) 号点向所有点连一条长度为 \(0\) 的边,跑最短路即可。

(Tips:差分约束的式子也可以拿来跑最长路)

我们不清楚图的具体形态,所以要注意判断负环。当存在负环时(大多数时候使用 SPFA 来做),差分约束系统无解。

最短路结束之后,若差分约束系统有可行解,一组可行解即为 \(x_i=dis_i\)

复杂度就是 SPFA 的复杂度,最坏 \(O(nm)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,m,tot,head[10005],dis[10005],cnt[10005];
bool vis[10005];
struct node{
	int from,to,w,nxt;
}e[20005];
inline void add_edge(int u,int v,int w){
	e[++tot].from=u;
	e[tot].to=v;
	e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}
queue<int>q;
inline bool SPFA(){
	vis[0]=1;
	q.push(0);
	memset(dis,63,sizeof(dis));
	dis[0]=0;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=e[i].nxt){
			int u=x,v=e[i].to,w=e[i].w;
			if(dis[v]>=dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					cnt[v]++;
					vis[v]=1;
					if(cnt[v]>n){
						return 1;
					}
					q.push(v);
				}
			}
		}
	}
	return 0;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		add_edge(0,i,0);
	}
	for(int i=1;i<=m;i++){
		int x,y,k;
		cin>>x>>y>>k;//x-y<=k   ->   x<=y+k
		add_edge(y,x,k);
	}
	if(SPFA()){
		cout<<"NO"<<endl;
	}
	else{
		for(int i=1;i<=n;i++){
			cout<<dis[i]<<" ";
		}
	}
	return 0;
}

P1993 小 K 的农场

Description

给你 \(m\) 个形式如下的不等式,让你判断这个 \(n\) 元一次不等式组是否存在合法的正整数解。

  • 1 a b c 表示 \(A_a-A_b\ge c\)

  • 2 a b c 表示 \(A_a-A_b\le c\)

  • 3 a b 表示 \(A_a=A_b\)

Solution

想办法转化一下。

对于操作 1:

\[A_a-A_b\ge c \]

两边同时加 \(A_b\)

\[A_a\ge A_b+c \]

移项成一个差分约束的形式即可。

\[A_b\le A_a-c \]

加边的代码:add_edge(a,b,-c)

同理,操作二可化为 add_edge(b,a,c)

操作三,\(A_a=A_b\)

可以分成两个不等式来看:\(A_a\ge A_b\)\(A_a\le A_b\)

add_edge(a,b,0);
add_edge(b,a,0);

Tips:由于最后一个操作会建两条边,所以最坏情况下边数是 \(2\times m+n\) 的,注意边数组的大小。

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,m,tot,head[10005],vis[10005],dis[10005],cnt[10005];
struct node{
	int from,to,w,nxt;
}e[15005];
inline void add_edge(int u,int v,int w){
	e[++tot].from=u;
	e[tot].to=v;
	e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
	return;
}
queue<int>q;
inline bool SPFA(){
	memset(dis,63,sizeof(dis));
	dis[0]=0;
	vis[0]=1;
	q.push(0);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to,w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					vis[v]=1;
					cnt[v]++;
					if(cnt[v]>=n){
						return 1;
					}
					q.push(v);
				}
			}
		}
	}
	return 0;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		add_edge(0,i,0);
	}
	for(int i=1;i<=m;i++){
		int opt;
		cin>>opt;
		if(opt==1){
			int a,b,c;//b<=a-c
			cin>>a>>b>>c;
			add_edge(a,b,-c);
		}
		else if(opt==2){
			int a,b,c;//a<=b+c
			cin>>a>>b>>c;
			add_edge(b,a,c);
		}
		else{
			int a,b;
			cin>>a>>b;//b<=a+0   &&   a<=b+0
			add_edge(a,b,0);
			add_edge(b,a,0);
		}
	}
	if(SPFA()){
		cout<<"No"<<endl;
	}
	else{
		cout<<"Yes"<<endl;
	}
	return 0;
}

P4926 [1007] 倍杀测量者

Description

给出 \(s\) 个条件和 \(t\) 个数组 \(A\) 中的值,让你求出最大的正实数 T,使得不全满足条件。

  • 1 a b k\(A_a\ge (k-T)\times A_b\)

  • 2 a b k\(A_b < (k+T)\times A_a\)

\(1\le n,s\le 10^3\)

Solution

容易想到二分 \(T\),主要讲讲如何转化条件。

opt 1

\[A_a\ge (k-T)\times A_b \]

\[\log (A_a)\ge \log(k-T)+\log(A_b) \]

add_edge(b,a,log(k-T));

opt 2

\[A_b < (k+T)\times A_a \]

\[\log(A_b)<\log(k+T)+ \log(A_a) \]

\[\log(A_a) > \log(A_b)-\log(k+T) \]

add_edge(b,a,-log(k+T))

正确性被题目中的 1e-4 保证了。

最坏复杂度为 \(O(nm)\),可以通过。

Tips:如果 \(T=0\) 时却能满足所有条件,输出 -1

posted @ 2025-08-20 23:24  Creativexz  阅读(9)  评论(0)    收藏  举报