算法学习笔记(13):同余最短路

同余最短路

是一种通过同余把状态分类, 再通过建图跑最短路解决问题的算法。可以高效率解决一些特定的问题。 非常的奇妙。

算法

鉴于学不懂, 所以直接搬 oiwiki 的题吧。 呜呜呜。

P3403 跳楼机

有一栋高为 h 的楼, 初始在一楼, 每次可以向上移动 xyz 层, 也可以回到一楼, 询问可以到达楼层的数量。
转化问题, 可以看成有多少个 i, 满足 i=ax+by+cz, 考虑如果可以跳到1, 那么 1+x1+x+x...,
都可以跳到, 可以跳到 2, 那么 2+x2+x+x...都可以跳到, 并且答案不重复, 所以考虑按 mod x 的剩余系来, 分段统计答案, 我们只需要计算 p mod x=i能跳到的最低楼层, 因为有些跳不到。
如果这里懂了, 那么建边也就懂了。

  1. i>(i+y) mod x 边权为 y
  2. i>(i+z) mod z 边权为 z

最后从 1 开始跑最短路。 再统计答案就可以了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
const ll INF = LONG_LONG_MAX; 
int X, Y, Z, head[N], tot, vis[N];
ll H, d[N];
struct Edge{ int to, dis, nxt; }edge[N << 3];
void add(int x, int y, int z) { edge[++tot] = {y, z, head[x]}; head[x] = tot; }
#define fi first
#define se second
priority_queue< pair<ll, int> > q;
void dijk(int s) {
	fill(d, d + X, INF);
	d[s] = 1; q.push({-d[s], s});
	while (!q.empty()) {
		int u = q.top().se; q.pop();
		if (vis[u]) continue;
		vis[u] = 1;
		for (int i = head[u]; i; i = edge[i].nxt) {
			int v = edge[i].to;
			if (!vis[v] && d[u] + edge[i].dis < d[v]) {
				d[v] = d[u] + edge[i].dis;
				q.push({-d[v], v});
			}
		} 
	}
}
int main() {
	scanf("%lld%d%d%d", &H, &X, &Y, &Z);
	if (X == 1 || Y == 1 || Z == 1) {
    	printf("%lld\n", H);
    	return 0;
  	}
	for (int i = 0; i < X; i++) {
		add(i, (i + Y) % X, Y);
		add(i, (i + Z) % X, Z);
	}
	dijk(1);
	ll ans = 0; 
	for (int i = 0; i < X; i++) if (H >= d[i]) ans += (H - d[i]) / X + 1;
	printf("%lld", ans);
	return 0;
}

[ABC077D] Small Multiple

输出 K 的正整数倍的最小数位累加和。

考虑很难办, 最质朴的做法就是枚举左右的倍数, 显然不行。 但是我们把所有数按数位和分类, sum=1 的有1,10,100,1000..., sum=2 的有 2,11,101,110,1001..., 这样就会很难发现一个性质, 一个数一定可以由 若干+1×10操作过来, 且 +1操作产生1的代价, ×10 不产生代价, 这时候就可以跑同余最短路, 并且这还是01图, 直接01BFS就可以做到时间复杂度 O(E)。 答案就是 10 的最短路, 严谨的你会发现, 如果连续跑10次边权为1的边, 得出来的答案不是错的?这种情况显然不合法, 但是我们跑的是最短路, 跑10次边权为1的边, 肯定不优。 所以不影响答案。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int K, d[N], vis[N];
#define pf push_front
#define pb push_back
#define ls ((u * 10) % K)
#define rs ((u + 1) % K)
deque<int> q;
void bfs(int s) {
	memset(d, 0x3f, sizeof d);
	q.pb(s); d[s] = 1;
	while (!q.empty()) {
		int u = q.front(); q.pop_front();
		if (u == 0) printf("%d", d[u]), exit(0);
		vis[u] = 1;
		if (!vis[ls] && d[ls] > d[u]) d[ls] = d[u], q.pf(ls);
		if (!vis[rs] && d[rs] > d[u] + 1) d[rs] = d[u] + 1, q.pb(rs);
	}
}
int main() {
	scanf("%d", &K);
	bfs(1);
	return 0;
}

牛场围栏

考虑和跳楼机十分相似, 我们钦定一个最小的木棍为模数 Min (因为这样边数最少), 然后O(N×M×MIN)求建图跑同余最短路, 就可以找到每一个同余类的最小的可以围成的围栏, Min就是最大的不能围成的围栏。

墨墨的等式

考虑是牛场围栏和跳楼机的整合版, 答案统计的时候魔改一下就行了。

posted @   qqrj  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示