【学习笔记】差分约束系统

【图论】差分约束系统

前置芝士

SPFA判负环与最短路

SPFA判负环

负环定义:边の权值之和为负数的环

不会真的有人不会SPFA吧

先放张图

就是在SPFA跑最短路的时候判断一下有没有一个点被访问的次数大于n,因为对于任意一个1能够到达的却不存在负环的路径,这条路径上每个节点被访问的次数一定是1~n,否则其中一定会有负环存在(可以自己举例验证一下)

板子 AC代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>
#define int long long

using namespace std;

const int maxn=3e3+5;

inline int read()
{
	int w=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')
		{
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w*f;
}

int n,m,t;

int tot;

int cnt[maxn];

int dis[maxn];

bool vis[maxn];

int head[maxn];

struct edge
{
	int to;
	int val;
	int from;
	int next;
}e[2*maxn];

void add(int x,int y,int z)
{
	tot++;
	e[tot].to=y;
	e[tot].from=x;
	e[tot].val=z;
	e[tot].next=head[x];
	head[x]=tot;
}

bool spfa()
{
	queue <int> q;
	
	q.push(1);
	dis[1]=0;
	vis[1]=true;
	cnt[1]=1;
	
	while(!q.empty())
	{
		int k=q.front();
		
		q.pop();
		
		vis[k]=false;
		
		for(int i=head[k];i;i=e[i].next)
		{
			int to=e[i].to;
			
			if(dis[to]>dis[k]+e[i].val)
			{
				dis[to]=dis[k]+e[i].val;
				if(vis[to]==false)
				{
					vis[to]=true;
					q.push(to);
					cnt[to]++;
					if(cnt[to]>n)
					{
						return true;
					}
				}
			}
		}
	}
	
	return false;
}

signed main()
{
	t=read();
	
	while(t--)
	{
		tot=0;
		memset(dis,0x3f3f3f,sizeof(dis));
		memset(cnt,0,sizeof(cnt));
		memset(vis,false,sizeof(vis));
		memset(e,0,sizeof(e));
		memset(head,0,sizeof(head));
		
		n=read();
		m=read();
	
		for(int i=1;i<=m;i++)
		{
			int a=read();
			int b=read();
			int c=read();
			add(a,b,c);
			if(c>=0)
			{
				add(b,a,c);
			}
		}
	
		if(spfa())
		{
			cout<<"YES"<<endl;
		}
		else
		{
			cout<<"NO"<<endl;
		}	
	}
	
	return 0;
}

差分约束系统

给出一组包含 \(m\) 个不等式,有 \(n\) 个未知数的形如:

\[\begin{cases} x_{c_1}-x_{c'_1}\leq y_1 \\x_{c_2}-x_{c'_2} \leq y_2 \\ \cdots\\ x_{c_m} - x_{c'_m}\leq y_m\end{cases} \]

的不等式组,求任意一组满足这个不等式组的解。

思考转化为图论

对于每个形如\(x1-x2\leq y\)的不等式,都可以转化为\(x1\leq x2+y\)

看着这个式子就很眼熟的样子

if(dis[to]>dis[k]+e[i].val)
{
    dis[to]=dis[k]+e[i].val;
}

这不就是单源最短路径??

所以对于每个式子\(x1-x2\leq y\),都可以看做x2到x1有一条权值为y的连边,那么每个x对于源点0的最短路径长度dis[x]一定是满足不等式的一组解,而当有负环存在时,一定无解

其实还是SPFA跑0到各个点的单源最短路径在加上判负环啦,就是个SPFA的板子

板子代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>

using namespace std;

const int maxn=5e3+5;

inline int read()
{
	int w=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')
		{
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w*f;
}

int tot;

int n,m;

int dis[maxn];

int head[maxn];

bool vis[maxn];

int cnt[maxn];

struct edge
{
	int from;
	int to;
	int val;
	int next;
}e[maxn*2];

void add(int x,int y,int z)
{
	tot++;
	e[tot].to=y;
	e[tot].from=x;
	e[tot].val=z;
	e[tot].next=head[x];
	head[x]=tot;
}

bool spfa()
{
	queue <int> q;
	
	memset(dis,0x3f3f3f,sizeof(dis));
	memset(vis,false,sizeof(vis));
	
	q.push(0);
	dis[0]=0;
	cnt[0]=1;
	vis[0]=true;
	
	while(!q.empty())
	{
		int k=q.front();
		
		q.pop();
		
		vis[k]=false;
		
		for(int i=head[k];i;i=e[i].next)
		{
			int to=e[i].to;
			
			if(dis[to]>dis[k]+e[i].val)
			{
				dis[to]=dis[k]+e[i].val;
			
				if(vis[to]==false)
				{
					vis[to]=true;
					cnt[to]++;
					q.push(to);
					if(cnt[to]>n+1)
					{
						return true;
					}
				}
			}
		}
	}
	
	return false;
}

int main()
{
	n=read();
	m=read();
	
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		u=read();
		v=read();
		w=read();
		add(v,u,w);
	}
	
	for(int i=1;i<=n;i++)
	{
		add(0,i,0);//要记得把0和各个点加进去,不然0就是一个单独的节点了
	}
	
	if(!spfa())
	{
		for(int i=1;i<=n;i++)
		{
			cout<<dis[i]<<" ";
		}
	}
	else
	{
		cout<<"NO"<<endl;
	}
	
	return 0;
}

推几道水题

工程规划

解析:套板子即可

AC 代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<climits>
#include<queue>
#include<algorithm>

using namespace std;

const int maxn=5010;

inline int read()
{
	int w=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')
		{
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w*f;
}

int n,m;

int tot;

int cnt[maxn];

int dis[maxn];

bool vis[maxn];

int head[maxn];

struct edge
{
	int val;
	int to;
	int from;
	int next;
}e[2*maxn];

void add(int x,int y,int z)
{
	tot++;
	e[tot].to=y;
	e[tot].from=x;
	e[tot].val=z;
	e[tot].next=head[x];
	head[x]=tot;
}

bool spfa()
{
	queue <int> q;
	
	memset(dis,0x3f3f3f,sizeof(dis));
	
	q.push(0);
	vis[0]=true;
	cnt[0]++;
	dis[0]=0;
	
	while(!q.empty())
	{
		int k=q.front();
		
		q.pop();
		
		vis[k]=false;
		
		for(int i=head[k];i;i=e[i].next)
		{
			int to=e[i].to;
			
			if(dis[to]>dis[k]+e[i].val)
			{
				dis[to]=dis[k]+e[i].val;
				if(vis[to]==false)
				{
					vis[to]=true;
					cnt[to]++;
					q.push(to);
					if(cnt[to]>n+1)
					{
						return true;
					}
				}
			}
		}
	}
	
	return false;
}

int main()
{
	n=read();
	m=read();
	
	for(int i=1;i<=m;i++)
	{
		int u=read();
		int v=read();
		int w=read();
		add(v,u,w);
	}
	
	for(int i=1;i<=n;i++)
	{
		add(0,i,0);
	}
	
	int minn=INT_MAX;

	if(spfa())
	{
		cout<<"NO SOLUTION";
	}
	else
	{
		for(int i=1;i<=n;i++)
		{
			minn=min(minn,dis[i]);
		}
		for(int i=1;i<=n;i++)
		{
			cout<<dis[i]-minn<<endl;
		}
	}
	
	return 0;
}

小K的农场

解析:也是板子。。。水的一批

AC 代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>

using namespace std;

const int maxn=5e3+10;

inline int read()
{
	int w=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')
		{
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w*f;
}

int head[maxn];

int tot,n,m;

int cnt[maxn];

int dis[maxn];

bool vis[maxn];

struct edge
{
	int from;
	int to;
	int val;
	int next;
}e[maxn<<4];

void add(int x,int y,int z)
{
	tot++;
	e[tot].to=y;
	e[tot].from=x;
	e[tot].val=z;
	e[tot].next=head[x];
	head[x]=tot;
}

bool spfa()
{
	queue <int> q;
	
	memset(dis,0x3f3f3f,sizeof(dis));
	
	q.push(0);
	vis[0]=true;
	dis[0]=0;
	cnt[0]++;
	
	while(!q.empty())
	{
		int k=q.front();
		
		q.pop();
		
		vis[k]=false;
		
		for(int i=head[k];i;i=e[i].next)
		{
			int to=e[i].to;
			
			if(dis[to]>dis[k]+e[i].val)
			{
				dis[to]=dis[k]+e[i].val;
				if(vis[to]==false)
				{
					vis[to]=true;
					q.push(to);
					cnt[to]++;
					if(cnt[to]>n+1)
					{
						return true;
					}
				}
			}
		
		}
	}
	
	return false;
}

int main()
{
	n=read();
	m=read();
	
	for(int i=1;i<=m;i++)
	{
		int opt=read();
		
		if(opt==1)
		{
			int u=read();
			int v=read();
			int w=read();
			add(u,v,-w);
		}
		if(opt==2)
		{
			int u=read();
			int v=read();
			int w=read();
			add(v,u,w);
		}
		if(opt==3)
		{
			int u=read();
			int v=read();
			add(u,v,0);
			add(v,u,0);
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		add(0,i,0);
	}
	
	if(spfa())
	{
		cout<<"No";
	}
	else
	{
		cout<<"Yes";
	}
	
	return 0;
}

补充&总结

对于一些题目,其约束条件可能会形如\(x1-x2\geq y\),我们的解决方案通常是两边同时乘上-1,得到\(x2-x1\leq -y\),然后就和上面一样力,而当出现x1-x2=y时,我们可以拆分将其为\(x1-x2\leq y\)\(x1-x2\geq y\)两个约束条件进行维护即可

To 初赛失利のOIer

尘埃纵然很轻,但是尘埃永远领略不到云彩有多轻。因为云彩在尘埃遥不可及的平流层之上,俯瞰着一切,而尘埃也至多只能看见云彩投在地下的一段阴影。

但是呢,话也不能这么说。云彩随着大气环流不断更新,这一朵可能再不会与我这种尘埃相遇。所谓的阴影也只是一瞬罢了。

尘埃…也应该在地壳表面拥有自己的精彩啊。

posted @ 2022-09-19 14:46  NinT_W  阅读(21)  评论(0编辑  收藏  举报