Loading

「学习笔记」同余最短路

同余最短路,可以用来解决像“给定 \(n\) 个整数,求这 \(n\) 个整数能拼凑出多少的其他整数(\(n\) 个整数可以重复取),以及给定 \(n\) 个整数,求这 \(n\) 个整数不能拼凑出的最小(最大)的整数,或者至少要拼几次才能拼出模 \(K\)\(p\) 的数”的问题。
同于最短路是利用同余来构造状态,状态转移通常为 \(f_{i + j} = f_i + j\),类似于 \(dis_{v} = dis_u + e_{u, v}\)


\(n\) 个数,分别为 \(a_1, a_2, a_3, a_4, \cdots, a_n\) ,算出这 \(n\) 个数最大的不能拼出的数,\(50 \le n \le 10^7\)

某凯的疑惑?
只能说很像,但不完全是,毕竟这有 \(n\) 个数,某凯的疑惑是两个数。
这里,我们就可以用同余最短路来做了,首先,取出 \(\min_1^n(a_i)\) 来作为我们的模数 \(mod\),对于一个余数 \(x (0 \le x \le mod - 1)\),会有一个数 \(k\),使得 \(k \cdot mod + x\) 可以被拼出而 \(k \cdot (mod - 1) + x\) 不能被拼出,那么,对于 \(x\) 来说,\(k \cdot (mod - 1) + x\) 就是最大的不能被拼出的数,对于每一个余数,我们会发现,都有一个 \(k\) 可以满足这样的关系(\(k\) 可能为负数),因此我们只需要找出这些数中最大的数即可。具体如何操作,看代码。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pil;
#define fir first
#define sec second

const int N = 1e7 + 5;

int n, m, seed, mod;
int a[N];
ll dis[N];
bool vis[N];

void dijkstra() {
	priority_queue<pil, vector<pil>, greater<pil>> q;
	for (int i = 0; i <= mod; ++ i) {
		dis[i] = 1e18;
	}
	q.push({0, 0});
	dis[0] = 0;
	while (!q.empty()) {
		pil it = q.top();
		q.pop();
		int u = it.second;
		if (dis[u] != it.first)	continue;
		for (int i = 1; i <= n; ++ i) {
			int v = (u + a[i]) % mod;
			if (dis[v] > dis[u] + a[i]) {
				dis[v] = dis[u] + a[i]; // 找到最小的能被拼出的数
				q.push({dis[v], v});
			}
		}
	}
}

int main() {
	scanf("%d%d%d", &n, &m, &seed);
	mt19937 rng(seed);
	auto get = [&]() {
		uniform_int_distribution<int> qwq(2, m);
		return qwq(rng);
	};
	mod = m;
	for (int i = 1; i <= n; i++) {
		a[i] = get();
		mod = min(mod, a[i]);
	}
	dijkstra();
	ll ans = -1;
	for (int i = 0; i < mod; ++ i) {
		ans = max(ans, dis[i] - mod);
		// dis 中存的是最小的能被拼出的数
		//所以只要再 -mod,就是最大的不能拼出的数
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2023-05-21 17:40  yi_fan0305  阅读(40)  评论(0编辑  收藏  举报