【学习】【补题】P3403 跳楼机 P5960 【模板】差分约束 同余最短路、spfa的使用

此篇主要是记录关于spfa与同余最短路的贴,那么先按照难度顺序,或者说技能树顺序来吧。
文末附上了我看过的所有帖子,基本来源于洛谷,如果其他人看到了这个学习记录,看我的感觉讲的不够清楚,没理解,请自行移步到这些原帖,一定是很容易懂的

P5960 【模板】差分约束

题意:

也就是给出n个数,然后这n个数有m个不等式,问满足要求的一组解。

思路:

本人太笨,一开始还觉得只要借助不等式算区间得值之类的就能做出来

1.想联想到最短路,转换成图论问题你必须要观察到   x_{c1}\leq x_{c1^{'}}+y1,这个公式与最短路非常相似,不然是转换不到图论去的。能转换成图的关系就是,两个点之间的差值不能超过y1,如此一来,最短路就刚好适用,因为最短路可以提供两个点的间接关系,这是这道题的关键。

2.接下来要加入超级源点,因为不等式提供的是相对关系,你总得要一个具体的值对吧。但是不能是任意挑一个点,因为构建出来的图并不一定是连通图,设置所有的点与超级源点的路径为0
x-y\leq 0,因为都是相对关系,所以不影响结果的得出,这样就联通了整个图,一次就可以了,不过我觉得改成联通图独立判断应该也能行但是没啥必要

3.接下来就是spfa的板子,推荐看D03 最短路 Bellman-Ford 算法 SPFA 算法——信息学奥赛算法_哔哩哔哩_bilibili
添边是不能双向边的,这并不是无向边,x_{c1}\leq x_{c1^{'}}+y1的关系只能得到单向边,如果你构建无向边(双向边),其实是创造了一个新关系
这个图出现负环的情况是什么,负环其实就是x\leq x+k,自己小于等于自己是错误的,很明显是NO答案。负环是指自身环无限更新至-∞的情况,并非有负边就是负环,其实负环就是互相要求对方在关系传递后一个比一个小,又要比对方大又要比对方小是冲突的。如果能保持稳定存在的关系,就不是NO

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define int128 __int128
#define endl '\n'
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
const int N = 2e5+10;
const int INF = 1e18;
const int MOD = 2023;

int n,m;
vector<pair<int,int> > mp[N];
int vis[N];
int dis[N];
int cnt[N];

bool spfa(int x){
	queue<int> q;
	vis[x]=1;
	dis[x]=0;
	q.push(x);
	while(q.size()){
		auto t=q.front();
		q.pop();
		vis[t]=0;
		for(auto [u,v] : mp[t]){
			if(dis[u]>dis[t]+v){
				dis[u]=dis[t]+v;
				if(vis[u]) continue;
				vis[u]=1;
				cnt[u]++;
				if(cnt[u]==n+1) return false;
				q.push(u);
			}
		}
		
	}
	return true;
}

void solve(){
	cin >> n >> m;
	for(int i=0;i<m;i++){
		int x,y,c;
		cin >> x >> y >> c;
		mp[y].push_back({x,c});
	}
	
	for(int i=1;i<=n;i++){
		dis[i]=INF;
		mp[0].push_back({i,0});
	}
	
	if(!spfa(0)){
		cout << "NO" << endl;
	}
	else{
		for(int i=1;i<=n;i++){
			cout << dis[i] << " ";
		}
	}
	
}

signed main(){
	IOS;
	
	int t=1;
//	cin >> t;
	while(t--){
		solve();
	}
}

那么spfa用于判负环的最短路初步学习就到这了。

总结:数学关系能转换成最短路联系,因为最短路能保持着原有的传递关系,且spfa能解决负环问题。

P3403 跳楼机

题意:

思路:

1.这题先可以转换成给出3个数字操作,能得到哪些数字ax+by+cz==k,但没有要求最小操作数。所以可以看成,任何一个新得到的数加上别的数能得到的数,现在我们假设得到了一个数字,它可以+a*k,也就是构成了一个类似于周期循环一样的,是不是代表一个数的相同余数的最小值被得到后,之后处于相同余数(每个周期的相同余数)的所有值都可以被得到?

这个思路可能并不好不适用所有人,该想法来自蓝桥杯的一道题,我当时并不会同余最短路……但是思路想到了同余最短路……P8646 [蓝桥杯 2017 省 AB] 包子凑数 题解 - 洛谷专栏如果感兴趣可以看这个。

2.现在明确如果知道一个同一个余数的最小值知道后,那么在这个相同余数下的所有以a构成循环的数之后一定能通过+a得到。那么问题转换成了,怎么求出每个%a的余数,最小的数是谁。
那么很明显最短路利用剩下的b,c得到就行了,(x+a)%a==x,a的使用是无意义的。

3.如果还用1的话,明显加大了代码实现的复杂度,不妨h--,然后转换成0到h-1的高度进行余数的计算就可以了。后面数字的计算其实就是(h-dis[x])/a+1,这个自己画个图或者想想应该能想明白吧?类似于还有多少个周期这样的意思。然后这道题可以不用spfa得到,因为没有负权,如此一来也不存在负环,可以用dij,其实都是一样的。

spfa代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define int128 __int128
#define endl '\n'
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
const int N = 2e5+10;
const int INF = 1e18;
const int MOD = 2023;

int dis[N];
int vis[N];
int cnt[N];
vector<pair<int,int> > mp[N];

bool spfa(int x,int a){
	queue<int> q;
	vis[x]=1;
	dis[x]=0;
	q.push(0);
	while(q.size()){
		auto t=q.front();
		q.pop();
		vis[t]=0;
		
		for(auto [u,v] : mp[t]){
			if(dis[u]>dis[t]+v){
				dis[u]=dis[t]+v;
				if(vis[u]) continue;
				vis[u]=1;
				cnt[u]++;
				q.push(u);
			}
		}
		
	}
	return true;
}

void solve(){
	int h;
	cin >> h;
	h--;
	int a,b,c;
	cin >> a >> b >> c;
	
	for(int i=0;i<=a;i++){
		dis[i]=INF;
		mp[i].push_back({(i+b)%a,b});
		mp[i].push_back({(i+c)%a,c});		
	}
	
	spfa(0,a);
	
	int ans=0;
	for(int i=0;i<a;i++){
		if(dis[i]<=h){
			ans+=(h-dis[i])/a+1;
		}
	}
	
	cout << ans << endl;
	
}

signed main(){
	IOS;
	
	int t=1;
//	cin >> t;
	while(t--){
		solve();
	}
}

dij代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define int128 __int128
#define endl '\n'
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
const int N = 2e5+10;
const int INF = 1e18;
const int MOD = 2023;

int dis[N];
int vis[N];

void dij(int n,int b,int c){
	
	priority_queue<pair<int,int> ,vector<pair<int,int>> , greater<pair<int,int>> > q;
	dis[0]=0;
	q.push({0,0}); //点  最短路
	while(q.size()){
		auto [v,u]=q.top();
		q.pop();
		
		if(vis[u]) continue;
		vis[u]=1;
		
		int nex=(u+b)%n;
		if(dis[nex]>dis[u]+b){
			dis[nex]=dis[u]+b;
			q.push({dis[nex],nex});
		}
		nex=(u+c)%n;
		if(dis[nex]>dis[u]+c){
			dis[nex]=dis[u]+c;
			q.push({dis[nex],nex});
		}
		
	}
}

void solve(){
	int h;
	cin >> h;
	h--;
	int a,b,c;
	cin >> a >> b >> c;
	
	for(int i=0;i<=a;i++){
		dis[i]=INF;
	}
	
	dij(a,b,c);
	
	int ans=0;
	for(int i=0;i<a;i++){
		if(dis[i]<=h){
			ans+=(h-dis[i])/a+1;
		}
	}
	
	cout << ans << endl;
	
}

signed main(){
	IOS;
	
	int t=1;
//	cin >> t;
	while(t--){
		solve();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值