「学习笔记」同余最短路
同余最短路,可以用来解决像“给定 \(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;
}
朝气蓬勃 后生可畏