同余最短路

同余最短路

与差分约束类似,都是最短路的一些神奇应用。

同余最短路可以解决类似这样的问题:

  • 给定 n 个数,求给定值域内,用这些数(可以重复取)能够拼出多少其他数。代表题目:P3403、P2371。
  • 给定 n 个数,求这 n 个整数不能拼出的最小/最大整数。代表题目:P2662。
  • 给定 n 个数,至少要拼几次才能拼出模 Kp 的数。代表题目:Unknown

P3403 跳楼机

为了方便,我们把楼层的值域从 [1,h] 调整为 [0,h1]

不妨假设 x<y<z,令 disi 为只通过 yz,需满足 pmodx=i 的最低楼层 p,也就是只用 yz 能拼出的模 xi 同余的最小数。可以得到两个状态:

  • iy(i+y)modx
  • iz(i+z)modx

就按照这两个状态建边,跑最短路,可以求出我们刚才所说的 disi,其中 i[0,n)。接下来考虑怎么用 disi 统计答案,因为 disi 是满足模 xi 同余的最低楼层,所以用值域减去 disi,再除以 x 并向下取整,就是它能提供的贡献。还要 +1,因为 di 所在楼层也算一次。

用公式表示出来如下:

ans=i=0x1(hdisix+1)[hdisi]

容易发现最后的时空复杂度只和 x 有关,这也是为什么 x 越小越好。

然后这题就做完了。注意细节,h<263,而 long long 的最大范围是 2631,所以得开 unsigned long long。

#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;

using pii=pair<int,int>;
constexpr int MAXN=1e5+5;
int h,x,y,z;
vector<pii>g[MAXN];
int dis[MAXN];
bool vis[MAXN];
void dijkstra(int s){
	for(int i=1;i<x;++i) dis[i]=ULONG_LONG_MAX;
	priority_queue<pii,vector<pii>,greater<pii>>q;
	q.emplace(0,s);
	while(!q.empty()){
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(auto vv:g[u]){
			int v=vv.first,w=vv.second;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.emplace(dis[v],v);
			}
		}
	}
}

signed main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin>>h>>x>>y>>z;
	--h;
	for(int i=0;i<x;++i){
		g[i].emplace_back((i+y)%x,y);
		g[i].emplace_back((i+z)%x,z);
	}
	dijkstra(0);
	int ans=0;
	for(int i=0;i<x;++i)	
		if(h>=dis[i])
			ans+=(h-dis[i])/x+1;
	cout<<ans<<'\n';
	return 0;
}

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

不过是上一道题的拓展。将值域从单一的 [0,h1] 变成了 [l,r],同时由三个数变成了 n 个数。

统计是同理的,加上 [0,r] 的答案再减去 [0,l1] 的答案即可。

signed main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin>>n>>l>>r>>x;
	--l;
	for(int i=2;i<=n;++i){
		cin>>y;
		for(int j=0;j<x;++j) g[j].emplace_back((j+y)%x,y);
	}
	dijkstra(0);
	int ans=0;
	for(int i=0;i<x;++i){
		if(r>=dis[i]) ans+=(r-dis[i])/x+1;
		if(l>=dis[i]) ans-=(l-dis[i])/x+1;
	}
	cout<<ans<<'\n';
	return 0;
}

AT_arc084_b [ABC077D] Small Multiple

注意到任何一个正整数都可以从 1 开始,按照某种顺序执行 ×10+1 操作得到。最后执行的 +1 操作个数就是这个数的数位和。往最短路方面考虑。

建边方式为:对于所有 0k<n,从 k 向 10kmodn 连边权为 0 的边,从 k 向 (k+1)modn 连边权为 1 的边。与一般的最短路不同的是,初始令 dis1=1,最后求的是 dis0 的值。用 01BFS 的技巧可以做到 O(n) 复杂度。

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

constexpr int MAXN=1e5+5;
vector<pair<int,int>>g[MAXN];
int dis[MAXN];
bool vis[MAXN];
void dijkstra(int s){
	memset(dis,0x3f,sizeof(dis));
	dis[s]=1;
	priority_queue<pair<int,int>>q;
	q.emplace(1,s);
	while(!q.empty()){
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(auto vv:g[u]){
			int v=vv.first,w=vv.second;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.emplace(-dis[v],v);
			}
		}
	}
}

int main(){
	int n;
	cin>>n;
	for(int k=0;k<n;++k){
		g[k].emplace_back((k*10)%n,0);
		g[k].emplace_back((k+1)%n,1);
	}
	dijkstra(1);
	cout<<dis[0]<<'\n';
	return 0;
}
posted @   Laoshan_PLUS  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示