差分约束学习笔记

文章详细解释了在处理最值问题(最大值对应最长路,最小值对应最短路)时如何转换不等式,以及如何运用SPFA算法解决单源问题。特别提到了在SPFA遇到超时时的优化策略,包括队列转栈和观察经过点数量。举例分析了AcWing中的几道题目,展示了具体应用方法。
摘要由CSDN通过智能技术生成

y总讲解

重点是 源点满足条件,建立的边的方向,求得是最短路还是最长路

 蓝书讲解

总结:


1. 题目中要求求出最大值,则要将所有的不等式变为形如 $x_i-x_j<=c_k$,求单源最短路。(1)若存在负环,则无解。 (2)若无法到达,则有无数种解。 (3)不满足上述两条,则有解。
2. 题目中要求求出最小值,则要将所有的不等式变为形如 $x_i-x_j>=c_k$, 求单源最长路。(1)若存在正环,则无解。 (2)若无法到达,则有无数种解。 (3)不满足上述两条,则有解。
3. 若不等式形如 $x_i-x_j=c_k$,则将其变为形如 $x_i-x_j>=c_k ~ ~ x_i-x_j<=c_k$
4. 若不等式形如 $x_i-x_j<c_k$,则将其变为形如 $x_i-x_j<=c_k-1$
5. 建图规则:$x_i-x_j$ 不等式关系 $c_k$,则让 $j$$i$ 连一条有向边,边权为 $c_k$。 

小技巧:

1. 若spfa判正/负环出现超时,则可以尝试使用将 队列 改为 来存储

2. 若spfa判正/负环出现超时,则可以认为如果经过的点的数量 count 很大时,如count>=2*N,则认为它有正/负环

3. 求最小值,则求最长路,大小关系全变成 x_i>=x_j+c ;求最大值,则求最短路,大小关系全变成 x_i<=x_j+c 。  连边时为 j 向 i 连一条长度为 c 的边。

例题:

1. AcWing 1169. 糖果

 求分发的最少糖果,所以求单源最长路

 这道题用队列的过不去,将队列改为栈,当spfa使用队列超时时再用这种优化,否则有时使用栈会超级慢

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=3e5+10;
#define ll long long 
struct node{
	int nex,to,w;
}e[M];
int head[N],cnt,tot[N],vis[N];
int n,m,q[N];
ll d[N];
void add(int u,int v,int w){
	e[++cnt].nex=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
bool spfa(){
	int hh=0,tt=1;
	memset(d,-0x3f,sizeof(d));
	d[0]=0; q[0]=0; vis[0]=1;
	while(hh!=tt){
		int u=q[--tt];
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].to,w=e[i].w;
			if(d[v]<d[u]+w){
				d[v]=d[u]+w;
				tot[v]=tot[u]+1;
				if(tot[v]>=n+1) return false;
				if(!vis[v]){
					q[tt++]=v;
					vis[v]=1;
				}
			}
		}
	} 
	return true;
}
int main(){
	scanf("%d%d",&n,&m);
	while(m--){
		int x,a,b;
		scanf("%d%d%d",&x,&a,&b);
		if(x==1) add(b,a,0),add(a,b,0);
		else if(x==2) add(a,b,1);
		else if(x==3) add(b,a,0);
		else if(x==4) add(b,a,1);
		else add(a,b,0);
	}
	for(int i=1;i<=n;i++) add(0,i,1);
	if(!spfa()) printf("-1\n");
	else{
		ll ans=0;
		for(int i=1;i<=n;i++) ans+=d[i];
		printf("%lld\n",ans);
	}
	return 0;
}
2. AcWing 362. 区间

 先将所有数向右平移一位,保证 i-1 不会变为 -1

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10,M=N*3+10;

struct node{
	int nex,to,w;
}e[M];
int head[N],cnt,vis[N],q[N];
int d[N];
int n;
void add(int u,int v,int w){
	e[++cnt].nex=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
void spfa(){
	memset(d,-0x3f,sizeof(d));
	int hh=0,tt=1;
	q[hh]=0; vis[0]=1; d[0]=0;
	while(hh!=tt){
		int u=q[hh++];
		if(hh==N) hh=0;
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].to,w=e[i].w;
			if(d[v]<d[u]+w){
				d[v]=d[u]+w;
				if(!vis[v]){
					q[tt++]=v;
					if(tt==N) tt=0;
					vis[v]=1;
				}
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=50001;i++){
		add(i-1,i,0);
		add(i,i-1,-1);
	}
	for(int i=1;i<=n;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		a++; b++;
		add(a-1,b,c);
	}
	spfa();
	printf("%d\n",d[50001]);
	return 0;
}
3. AcWing 1170. 排队布局

这道题先考虑 源点 0  是否可以到达每一个点,我们发现 i+1 向 i 连一条边,我们发现如果所有奶牛在数轴正半轴,则源点 0 无法到达所有点。由于题意说奶牛在数轴上任意位置,只是相对位置不变,因此我们把所有奶牛放在负半轴,这样 源点 0 就可以到达他们所有奶牛了。

对于该题求最大距离,则我们求最短路

对于第一小问,是否存在满足条件的方案,我们则把 n 个点全加入到队列里,省去了从源点 0 向他们每一个点连一条权值为 0 的边的操作。之后我们判断是否存在负环。若存在,则没有满足要求的方案;反之,则有方案

对于第二小问,我们则将所有奶牛全部向右平移,将第一个奶牛放在 位置为 0 的地方,那么 d[n] 则是 1 号点与 n 号点之间的距离。若 d[n]==INF ,则说明二者之间距离任意大,输出 -2

对于第三小问,则直接输出 d[n]

#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=2e4+N+10,INF=0x3f3f3f3f;
struct node{
	int nex,to,w;
}e[M];
int head[N],cnt,tot[N],vis[N],q[N],d[N];
int n,m1,m2;
void add(int u,int v,int w){
	e[++cnt].nex=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
bool spfa(int siz){
	memset(d,0x3f,sizeof(d));
	memset(tot,0,sizeof(tot));
	memset(vis,0,sizeof(vis));
	int hh=0,tt=0;
	for(int i=1;i<=siz;i++){
		q[tt++]=i;
		d[i]=0;
		vis[i]=1;
	}
	while(hh!=tt){
		int u=q[hh++];
		if(hh==N) hh=0;
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].to,w=e[i].w;
			if(d[v]>d[u]+w){
				d[v]=d[u]+w;
				tot[v]=tot[u]+1;
				if(tot[v]>=n) return false;
				if(!vis[v]){
					q[tt++]=v;
					if(tt==N) tt=0;
					vis[v]=1;
				}
			}
		}
	}
	return true;
}
int main(){
	scanf("%d%d%d",&n,&m1,&m2);
	for(int i=1;i<n;i++) add(i+1,i,0);
	while(m1--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		if(b<a) swap(a,b);
		add(a,b,c);
	}
	while(m2--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		if(b<a) swap(a,b);
		add(b,a,-c);
	}
	if(!spfa(n)) printf("-1\n");
	else{
		spfa(1);
		if(d[n]==INF) printf("-2\n");
		else printf("%d\n",d[n]);
	}
	return 0;
}
4. AcWing 393. 雇佣收银员

0 \sim 23 代表区间 [0,1] [1,2]\cdots [23,24] 共 24 个时间区间,我们将其共同向右平移一个小时,变成 1\sim 24,这样可以处理 i-1 变成 -1 的情况。

我们设 num[i] 表示当前时间 i 有几个人来申请工作,从时间 i 开始工作;设 x[i] 表示时间 i 实际上岗人数;设 s 为 x 的前缀和

#include<bits/stdc++.h>
using namespace std;
const int N=30,M=100;
struct node{
	int nex,to,w;
}e[M];
int head[N],tot[N],cnt;
int r[N],num[N],n;
int d[N],q[N],vis[N];
void add(int u,int v,int w){
	e[++cnt].nex=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
void build(int c){
	memset(head,0,sizeof(head));
	cnt=0;
	for(int i=1;i<=24;i++){
		add(i-1,i,0);
		add(i,i-1,-num[i]);
	}	
	for(int i=8;i<=24;i++) add(i-8,i,r[i]);
	for(int i=1;i<=7;i++) add(i+16,i,r[i]-c);
	add(0,24,c); add(24,0,-c);
}
bool spfa(int c){
	build(c);
	memset(d,-0x3f,sizeof(d));
	memset(vis,0,sizeof(vis));
	memset(tot,0,sizeof(tot));
	int hh=0,tt=1;
	q[0]=0; vis[0]=1; d[0]=0;
	while(hh!=tt){
		int u=q[hh++];
		if(hh==N) hh=0;
		vis[u]=0;
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].to,w=e[i].w;
			if(d[v]<d[u]+w){
				d[v]=d[u]+w;
				tot[v]=tot[u]+1;
				if(tot[v]>=25) return false;
				if(!vis[v]){
					q[tt++]=v;
					if(tt==N) tt=0;
					vis[v]=1;
				}
			}
		}
	}
	return true;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		for(int i=1;i<=24;i++)
			scanf("%d",&r[i]);
		memset(num,0,sizeof(num));
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			int t;
			scanf("%d",&t);
			num[t+1]++;
		}
		bool success=false;
		for(int i=0;i<=1000;i++){
			if(spfa(i)){
				printf("%d\n",i);
				success=true;
				break;
			}
		}
		if(success==false) printf("No Solution\n");
	}	
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值