同余最短路

更新日志 2025/04/01:开工。

概念

可以把几种形似数论问题的题通过同余性质转化为最短路问题。

比较抽象,所以直接通过例题来讲。

例题:跳楼机

题意等价于任意非负个 \(x,y,z\)\([0,h)\) 内能组合出多少个数。

我们考虑在\(x\) 意义下考虑问题,这么操作的意义是一个同余类可以通过这个同余类里最小的数加上任意一个 \(x\) 得到。

那么 \(y,z\) 操作的影响是:

\[i\in[0,x)\\ i\rightarrow(i+y)\bmod x\\ i\rightarrow(i+z)\bmod x\\ \]

然后我们发现一个同余类是没有上限的(无限加 \(x\)),但存在下限,也就是同余类内最小的数。我们考虑如何求出每个同余类的下限。

你发现我们可以根据上面两个操作建图,点数是 \(x\),边权就是这一个操作会给原来的数增加多少,那么每个点的最短路就是这个同余类的最小值

对于这道题,起点是 \(0\)

我们把同余最短路两个关键词提取出来,就变成了同余最短路。

代码

你会发现我的代码很冗余,这是因为我先做了下面那道习题,然后改一改代码就交了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define per(i,x,y) for(int i=(x);i>=(y);i--)
#define repl(i,x,y) for(int i=(x);i<(y);i++)
#define file(f) freopen(#f".in","r",stdin);freopen(#f".out","w",stdout);

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7/*998244353*/;

const int N=15;

int n;
ll l,r;
ll a[N];

const int NN=5e5+5;

ll dis[NN];
vec<pil> g[NN];
lrheap<pli> pq;

ll ans;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>r;r--;
	n=3;
	cin>>a[1];
	repl(i,1,a[1])dis[i]=INF;
	rep(i,2,n){
		cin>>a[i];
		repl(j,0,a[1])g[j].pub({(j+a[i])%a[1],a[i]});
	}
	pq.push({0,0});
	while(!pq.empty()){
		auto tp=pq.top();pq.pop();
		int now=tp.sec;
		if(tp.fir!=dis[now])continue;
		for(auto e:g[now]){
			int nxt=e.fir;ll val=e.sec;
			if(dis[nxt]>dis[now]+val){
				dis[nxt]=dis[now]+val;
				pq.push({dis[nxt],nxt});
			}
		}
	}
	repl(i,0,a[1]){
		if(dis[i]<=r)ans+=(r-dis[i])/a[1]+1;
		if(dis[i]<l)ans-=(l-1-dis[i])/a[1]+1;
	}
	cout<<ans;
    return 0;
}

习题:墨墨的等式

本质上只是多了一个值域下界而已。

代码
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef double db;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template <typename Type>
using vec=vector<Type>;
template <typename Type>
using grheap=priority_queue<Type>;
template <typename Type>
using lrheap=priority_queue<Type,vector<Type>,greater<Type> >;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define per(i,x,y) for(int i=(x);i>=(y);i--)
#define repl(i,x,y) for(int i=(x);i<(y);i++)
#define file(f) freopen(#f".in","r",stdin);freopen(#f".out","w",stdout);

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7/*998244353*/;

const int N=15;

int n;
ll l,r;
ll a[N];

const int NN=5e5+5;

ll dis[NN];
vec<pil> g[NN];
lrheap<pli> pq;

ll ans;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>l>>r;
	cin>>a[1];
	repl(i,1,a[1])dis[i]=INF;
	rep(i,2,n){
		cin>>a[i];
		repl(j,0,a[1])g[j].pub({(j+a[i])%a[1],a[i]});
	}
	pq.push({0,0});
	while(!pq.empty()){
		auto tp=pq.top();pq.pop();
		int now=tp.sec;
		if(tp.fir!=dis[now])continue;
		for(auto e:g[now]){
			int nxt=e.fir;ll val=e.sec;
			if(dis[nxt]>dis[now]+val){
				dis[nxt]=dis[now]+val;
				pq.push({dis[nxt],nxt});
			}
		}
	}
	repl(i,0,a[1]){
		if(dis[i]<=r)ans+=(r-dis[i])/a[1]+1;
		if(dis[i]<l)ans-=(l-1-dis[i])/a[1]+1;
	}
	cout<<ans;
    return 0;
}
posted @ 2025-04-01 13:12  LastKismet  阅读(47)  评论(0)    收藏  举报