曾记否,到中流击水,浪遏飞舟。|

Moyyer_suiy

园龄:2年7个月粉丝:4关注:18

同余最短路学习笔记

今天讲课讲到了同余最短路。简单记一下,防止之后忘了这个坑。

同余最短路 in oiwiki

简介

同余最短路,可以用来处理问题:

1.「给定 n 个数,求这些数能拼出多少其他数(选数数量不限)」

2.「给 n 个数,求这些数不能拼出的最大 or 最小值」

3.「至少拼几次才能拼出模 k 余 x 的数」。

同余最短路可以通过同余来构造状态,类比差分约束的转换思路,利用同余构造的状态可以看做单源最短路中的点。同余最短路的转移通常是:f(i+x)=f(i)+x,类似于普通最短路中的 f(v)=f(u)+d(u,v)

例题1:P3403 跳楼机

首先考虑,如果我只用 yz 拼新,即 ay+bz=k,对于每一个 k,在我以后处理的过程中,无论加多少个 x,其唯一不变的是 k 的这个值。

所以考虑对于与余数为 [0,x) 的数,我们实际上只需要保留最小的 ay+bz,这样才能在后面加 x 的处理中获得更大的数。

因此考虑,点编号就是余数,不断的往上添加 yz,以 [0,x) 为分别边的起点,以 f(i+x)=f(i)+x 作为转移,新的余数作为边的终点,加上的 yz 作为边权。

对其跑最短路,di 就是同余中的最小 k(达到这个余数需到达的最小楼层)。

因为最大限制为 h,那你从 k 通过 x 的倍数最多能翻出来 hkx+1(加一是考虑 x 的系数为 0)。答案加起来。

嗯我 x 写成 t 是因为两个变量名重了。调了一阵子。感谢机房大佬帮我调。

特别注意当 x1 时其模数没有意义,需要特判。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N=2e5+10;
ll h,ans;
int t,y,z;
priority_queue<pair<ll,int> >q;
int idx,e[N],w[N],nxt[N],head[N];
int vis[N];
ll d[N];
void add(int x,int y,int z){
	e[++idx]=y;
	w[idx]=z;
	nxt[idx]=head[x];
	head[x]=idx;
}
void dij(){
	memset(d,0x3f,sizeof d);
	d[1%t]=1;
	q.push(make_pair(-1,1%t));
	while(q.size()){
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=head[x];i;i=nxt[i]){
			if(d[e[i]]>d[x]+w[i]){
				d[e[i]]=d[x]+w[i];
				q.push(make_pair(-d[e[i]],e[i]));
			} 
		}
	}
}
int main(){
	scanf("%lld%d%d%d",&h,&t,&y,&z);
	for(int i=0;i<t;i++){
		add(i,(i+y)%t,y);
		add(i,(i+z)%t,z);
	}
	dij();
	for(int i=0;i<t;i++) if(h>=d[i]&&d[i]!=INF) ans+=((h-d[i])/t+1);
	printf("%lld",ans);
}

例题 2:P2371 [国家集训队] 墨墨的等式

做法类比上一题,对于以一个数作为基准建边跑同余最短路,最后对于区间类似前缀和统计。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e6+10;
ll l,r,ans;
ll d[N];
int n;
int vis[N];
int idx,e[N],w[N],nxt[N],head[N];
priority_queue<pair<int,int> > q;
void add(int x,int y,int z){
	e[++idx]=y;
	w[idx]=z;
	nxt[idx]=head[x];
	head[x]=idx;
}
void dij(){
	memset(d,0x3f,sizeof d);
	d[0]=0;
	q.push(make_pair(0,0));
	while(q.size()){
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=head[x];i;i=nxt[i]){
			if(d[e[i]]>d[x]+w[i]){
				d[e[i]]=d[x]+w[i];
				q.push(make_pair(-d[e[i]],e[i]));
			}
		}
	}
}
int main(){
	int x,y;
	scanf("%d%lld%lld%d",&n,&l,&r,&x);
	for(int i=1;i<n;i++){
		scanf("%d",&y);
		for(int i=0;i<x;i++) add(i,(i+y)%x,y); 
	}
	dij();
	l--;
	for(int i=0;i<x;i++){
		if(r>=d[i]) ans+=(r-d[i])/x+1;
		if(l>=d[i]) ans-=(l-d[i])/x+1;
	}
	printf("%lld",ans);
	return 0;
}

注意这个题,数组需要开 5e6。开大了会 MLE,小了会显示 TLE。别问我怎么知道的。

本文作者:Moyyer_suiy

本文链接:https://www.cnblogs.com/Moyyer-suiy/p/17814080.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Moyyer_suiy  阅读(303)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起