关于同余最短路

水博客太快乐了

同步于洛谷博客。
并不知道为什么要同步,也许某一天就因为懒不同步了。。。

虽然本蒟蒻很久之前就已经学过同余最短路了,但是当时并不完全懂,全是抄的代码。最近重新学了一下,发现其实很简单,完全不知道自己当时为什么不懂。。。
但是毕竟自己太水了,还是应该记录一下,万一以后又不会了该怎么办。。。

正文

那些看上去很神奇、不知道该用什么的题,不是 \(dp\) 就是图论。

同余最短路便是应用于如那位巨佬所说乍一看就很神奇的题中。。。
它虽说是图论,却也可以被理解为一种对 \(dp\) 的优化。。。更神奇了。。。(然而这种理解我觉得并不好理解,因此不加以赘述)

然而这种题一般代码都简单的要死,而且有题目有一种特定的套路:
题目一般会给出几个数或一个数列,且数列长度很短,要求的是用数列中的数能拼出的数在某一范围的个数(即满足 \(b\in [l,r] , b= \sum_{i=1}^{n} x_{i}a_{i}\) 的个数),数列中每个数使用的次数不限,范围一般会很大,因此不能用背包 \(dp\)

解法通常是找出这个数列中最小的,即为 \(minn\) ,设 \(t\) 为小于 \(minn\) 的一个数,每个数显然都可以被表示成 \(k*minn+t\) ,并且若 \(k*minn+t\) 可以合法,那么 \((k+i)*minn+t\) 也一定合法。因此对于每个小于 \(minn\) 的数 \(t\) 只要求出最小的 \(k\) 便很容易求出给定范围中合法的数的个数。
求解的过程可以使用最短路,并且因为数据很难构造,所以 \(SPFA\) 在这里还没死。

看一道例题

跳楼机

正如前面所说,乍一看这个题目不知道该用什么算法,但如果你精通各种套路,那么就会知道这是一个同余最短路的板子题。。。

先找出 \(x,y,z\) 中最小的,这里就假设它是 \(x\) 好了。
然后对于每个小于 \(x\)\(t\) ,分别连两条边 \((t,(t+y) \bmod x,y)\)\((t,(t+z) \bmod x,z)\) ,然后以 \(1\) 为起点跑最短路即可(但是在实际跑的时候并不需要把图建出来)。
注意 \(t=0\) 也要算到答案里。

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10, inf=0x7fffffffffffffff;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int x, y, z, h;
int dis[N], a[4];
bool vis[N];
queue<int > q;
signed main(void){
	h=read(), a[1]=read(), a[2]=read(), a[3]=read();
	int minn=min(min(a[1], a[2]), a[3]);
	if(minn==1) { printf("%lld\n",h); return 0; }
	for(int i=0; i<minn; i++) dis[i]=inf;
	dis[1]=1;
	q.push(1);
	while(!q.empty()){
		int u=q.front(); vis[u]=0; q.pop();
		for(int i=1;i<=3;i++){
			int v=(u+a[i])%minn;
			if(dis[v]>dis[u]+a[i]){
				dis[v]=dis[u]+a[i];
				if(!vis[v]) q.push(v), vis[v]=1;
			}
		}
	}
	int ans=0;
	for(int i=0; i<minn; i++) if(dis[i]<=h) ans+=(h-dis[i])/minn+1;
	printf("%lld\n",ans);
	return 0;
}

再看一道练习题吧。

[国家集训队]墨墨的等式

完全是套路题。。。

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=20, inf=0x7fffffffffffffff, M=5e5+10;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, l, r;
int a[N], dis[M];
bool vis[M];
queue<int> q;
signed main(void){
	n=read(), l=read(), r=read();
	for(int i=1; i<=n; ++i) a[i]=read();
	sort(a+1, a+1+n);
	for(int i=1; i<a[1]; ++i) dis[i]=inf;
	q.push(0);
	while(!q.empty()){
		int u=q.front(); vis[u]=0; q.pop();
		for(int i=1;i<=n;i++){
			int v=(u+a[i])%a[1];
			if(dis[v]>dis[u]+a[i]){
				dis[v]=dis[u]+a[i];
				if(!vis[v]) q.push(v), vis[v]=1;
			}
		}
	}
	int ans=0;
	for(int i=0; i<a[1]; i++){
		if(dis[i]==inf) continue;
		dis[i]/=a[1];
		ans+=max((r-i)/a[1]+1-max((l-1-i)/a[1]+1, dis[i]),0ll);
//		printf("%lld   %lld\n",dis[i],(r-i)/a[1]+1-max((l-1-i)/a[1]+1,dis[i]));
	}
	printf("%lld\n",ans);
	return 0;
}

放下代码就溜了。。。

posted @ 2021-06-15 22:10  Cyber_Tree  阅读(90)  评论(0编辑  收藏  举报