差分约束系统

Part 1:知识点

问题引入

差分约束系统是一种特殊的 \(n\) 元一次不等式组。它包含 \(n\) 个变量,以及 \(m\) 个形如 \(x_i-x_j\leq c_k\) 的约束条件。我们要解决的问题就是,求一组解 \(x_1=a_1,x_2=a_2,\dots,x_n=a_n\),使所有的约束条件得到满足

问题求解

每个约束条件 \(x_i-x_j\leq c_k\) 可以变形为 \(x_i\leq x_j+c_k\)。这和单源最短路中的三角形不等式 \(dist[y]\leq dist[x]+z\) 相似。因此,可以把每个变量 \(x_i\) 抽象成一个点 \(i\),对于每个约束条件,从 \(j\rightarrow i\) 连一条长度为 \(c_k\) 的有向边

为了防止图不连通,可以增加一个超级源点 \(0\),向每个点连一条长度为 \(0\) 的边。但这样会求出一组负数解,因为这样连边等价于多了 \(n\) 个形如 \(x_i-x_0\leq 0\) 的条件。但注意到如果 \(\{a_1,a_2,\dots ,a_n\}\) 是一组解,那么 \(\{a_1+\Delta,a_2+\Delta,\dots,a_n+\Delta\}\) 也是一组解,所以不必担心负数解的问题

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

若约束条件形如 \(x_i-x_k\geq c_k\),我们就改成计算单源最长路,若图中有正环则无解

模板

【模板】差分约束算法

#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\) 个区间,在区间 \([a_i,b_i]\) 中至少取任意互不相同的 \(c_i\) 个整数。求在满足 \(n\) 个区间的情况下,至少要取多少个正整数。

解题思路

  • \(x_i\) 表示 \(0\)\(i\) 取多少整数,那么对于每个区间 \([a_i,b_i]\),可构造不等式 \(x_{b_i}-x_{a_i-1}\geq c_i\)。所以从 \(a_i-1\rightarrow b_i\) 连一条长度为 \(c_i\) 的有向边

  • 又由定义知,\(0\leq x_i-x_{i-1}\leq 1\),所以从 \(i-1\rightarrow i\) 连一条长度为 \(0\) 的有向边,从 \(i\rightarrow i-1\) 连一条长度为 \(-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 拿到了秋名山的地图但却不知道每条路有多长。显然,为了赛车游戏的公平,每条 \(1\)\(n\) 的路径应当是等长的。mocania 想,我就随便给边标上一个 \(1...9\) 的长度,反正傻傻的 R 君也看不出来。

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

解题思路

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

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

  • 但注意到,有些边并不在 \(1\)\(n\) 的路径上,因此要先进行一次 \(\mathrm{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 @ 2023-08-07 19:54  xishanmeigao  阅读(54)  评论(0)    收藏  举报