差分约束系统

Part 1:知识点

问题引入

差分约束系统是一种特殊的 n 元一次不等式组。它包含 n 个变量,以及 m 个形如 xixjck 的约束条件。我们要解决的问题就是,求一组解 x1=a1,x2=a2,,xn=an,使所有的约束条件得到满足

问题求解

每个约束条件 xixjck 可以变形为 xixj+ck。这和单源最短路中的三角形不等式 dist[y]dist[x]+z 相似。因此,可以把每个变量 xi 抽象成一个点 i,对于每个约束条件,从 ji 连一条长度为 ck 的有向边

为了防止图不连通,可以增加一个超级源点 0,向每个点连一条长度为 0 的边。但这样会求出一组负数解,因为这样连边等价于多了 n 个形如 xix00 的条件。但注意到如果 {a1,a2,,an} 是一组解,那么 {a1+Δ,a2+Δ,,an+Δ} 也是一组解,所以不必担心负数解的问题

dist[0]=0,以 0 为起点跑一次单源最短路。若图中存在负环则无解。否则,xi=dist[i] 就是一组解

若约束条件形如 xixkck,我们就改成计算单源最长路,若图中有正环则无解

模板

【模板】差分约束算法

#include<bits/stdc++.h>
using namespace std;

const int N=5010,M=5010;

int n,m,d[N],cnt[N];
int head[N],ver[2*M],nxt[2*M],edge[2*M],tot;
bool v[N];
queue <int> q;

void add(int x,int y,int z)
{
	ver[++tot]=y;  edge[tot]=z;
	nxt[tot]=head[x];  head[x]=tot;
}

bool spfa()
{
	memset(d,0x3f,sizeof(d));
	d[0]=0;  v[0]=1;
	cnt[0]=-1;
	q.push(0);
	
	while(q.size())
	{
		int x=q.front();  q.pop();
		v[x]=0;
	
		for(int i=head[x]; i; i=nxt[i])
		{
			int y=ver[i],z=edge[i];
			if(d[y]>d[x]+z)
			{
				d[y]=d[x]+z;
				cnt[y]=cnt[x]+1;
				if(!v[y])
					q.push(y),v[y]=1;
			}
			if(cnt[y]>=n)
				return 0;
		}
	}
	
	return 1;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(y,x,z);
	}
	
	for(int i=1; i<=n; i++)
		add(0,i,0);
	
	if(!spfa())
		printf("NO");
	else
	{
		for(int i=1; i<=n; i++)
			printf("%d ",d[i]);
	}
	
	return 0;
}

Part 2:练习题

Intervals

题目传送门

题目大意

n 个区间,在区间 [ai,bi] 中至少取任意互不相同的 ci 个整数。求在满足 n 个区间的情况下,至少要取多少个正整数。

解题思路

  • xi 表示 0i 取多少整数,那么对于每个区间 [ai,bi],可构造不等式 xbixai1ci。所以从 ai1bi 连一条长度为 ci 的有向边

  • 又由定义知,0xixi11,所以从 i1i 连一条长度为 0 的有向边,从 ii1 连一条长度为 1 的有向边

  • 注意到 i 可能取到 0,所以整体右移。最后跑最长路即可

代码

#include<bits/stdc++.h>
using namespace std;

const int N=50010,M=50010*3;

int T,n,m,a,b,c,d[N];
int head[N],ver[M],nxt[M],edge[M],tot;
bool v[N];
queue <int> q;

void add(int x,int y,int z)
{
	ver[++tot]=y;  edge[tot]=z;
	nxt[tot]=head[x];  head[x]=tot;
}

void spfa()
{
	while(!q.empty())
		q.pop();
	memset(d,~0x3f,sizeof(d));
	d[0]=0;  v[0]=1;
	q.push(0);
	
	while(q.size())
	{
		int x=q.front();  q.pop();
		v[x]=0;
		
		for(int i=head[x]; i; i=nxt[i])
		{
			int y=ver[i],z=edge[i];
			if(d[y]<d[x]+z)
			{
				d[y]=d[x]+z;
				if(!v[y])
					q.push(y),v[y]=1;
			}
		}
	}
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(head,0,sizeof(head));  
		tot=0;
		n=0;
		
		scanf("%d",&m);
		for(int i=1; i<=m; i++)
		{
			scanf("%d%d%d",&a,&b,&c);
			add(a,b+1,c);
			n=max(n,b);
		}
		
		for(int i=0; i<=n; i++)
			add(i+1,i,-1),add(i,i+1,0);
			
		spfa();
		
		printf("%d\n",d[n+1]);
		if(T!=0)
			printf("\n");
	}
	
	return 0;
}

赛车游戏

题目传送门
双倍经验

题目大意

R 君和小伙伴打算一起玩赛车。但他们被老司机 mocania 骗去了秋名山。

秋名山上有 n 个点和 m 条边,R 君和他的小伙伴要从点 1 出发开往点 n,每条边都有一个初始的方向。老司机 mocania 拿到了秋名山的地图但却不知道每条路有多长。显然,为了赛车游戏的公平,每条 1n 的路径应当是等长的。mocania 想,我就随便给边标上一个 1...9 的长度,反正傻傻的 R 君也看不出来。

可 mocania 的数学不大好,不知道怎么给边标长度,只能跑来请教你这个 OI 高手了。

解题思路

  • dist[i] 表示 1i 的距离,又因为 dist[y]=dist[x]+edge(x,y),所以我们将边权的讨论转化成 dist[y]dist[x] 的讨论,这样只要求出一组 dist[1n],我们就可以算出每条边的长度,还保证了每一条 1n 的路径都是等长的

  • 因为 1edge(x,y)9,所以 1dist[y]dist[x]9,因此差分约束即可

  • 但注意到,有些边并不在 1n 的路径上,因此要先进行一次 dfs,只将有用的边添加到新图中。最后输出时没用的边输出 1 即可

代码

#include<bits/stdc++.h>
using namespace std;

const int N=1010,M=2010;

int n,m,d[N],cnt[N];
int head[N],from[M],ver[M],nxt[M],tot;
bool v[N],vis[N],ab[N]; //ab[x]表示x能否到达n
vector < pair<int,int> > g[N];
queue <int> q;

void add(int x,int y)
{
	ver[++tot]=y;  from[tot]=x;
	nxt[tot]=head[x];  head[x]=tot;
}

void add_edge(int x,int y) //差分约束建边
{
	g[x].push_back(make_pair(y,9));
	g[y].push_back(make_pair(x,-1));
}

bool dfs(int x)
{
	if(x==n || ab[x])
		return 1;
		
	vis[x]=1;
	for(int i=head[x]; i; i=nxt[i])
	{
		int y=ver[i];
		if(vis[y])
		{
			if(ab[y])
				add_edge(x,y),ab[x]=1;
			continue;
		}
		if(dfs(y))
			add_edge(x,y),ab[x]=1;
	}
	
	return ab[x];
}

bool spfa()
{
	memset(d,0x3f,sizeof(d));
	d[0]=0;  v[0]=1;
	cnt[0]=-1;
	q.push(0);
	
	while(q.size())
	{
		int x=q.front();  q.pop();
		v[x]=0;
		
		for(int i=0; i<g[x].size(); i++)
		{
			int y=g[x][i].first,z=g[x][i].second;
			if(d[y]>d[x]+z)
			{
				d[y]=d[x]+z;
				cnt[y]=cnt[x]+1;
				if(!v[y])
					q.push(y),v[y]=1;
			}
			if(cnt[y]>=n)
				return 0;
		}
	}
	
	return 1;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	
	if(!dfs(1)) //1无法到n
	{
		printf("-1");
		return 0;
	}
	
	for(int i=1; i<=n; i++)
		g[0].push_back(make_pair(i,0));
		
	if(!spfa())
		printf("-1");
	else
	{														
		printf("%d %d\n",n,m);
		for(int i=1; i<=tot; i++)
		{
			int x=from[i],y=ver[i];
			int z=d[y]-d[x];
			printf("%d %d %d\n",x,y,(z<1 || z>9)? 1:z);
		}
	}
	
	return 0;
}
posted @   xishanmeigao  阅读(39)  评论(0编辑  收藏  举报
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示