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

【图论】差分约束系统

前置芝士

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 个未知数的形如:

{xc1xc1y1xc2xc2y2xcmxcmym

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

思考转化为图论

对于每个形如x1x2y的不等式,都可以转化为x1x2+y

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

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

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

所以对于每个式子x1x2y,都可以看做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;
}

补充&总结

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

To 初赛失利のOIer

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

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

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

posted @   NinT_W  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示