「学习笔记」同余最短路
Posted 朝气蓬勃 后生可畏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「学习笔记」同余最短路相关的知识,希望对你有一定的参考价值。
同余最短路,可以用来解决像“给定 \\(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;
同余最短路
同余最短路其实是一种优化最短路建图的方法。
通常是解决给定m个整数,求这m个整数能拼凑出多少的其他整数(这m个整数可以重复取)或给定m个整数,求这m个整数不能拼凑出的最小(最大)的整数。
我们通过一道例题来讲解。
简化一下题意:用a,b,c(这里用a,b,c来代替x,y,z)三个数能组成几个小于h的整数。$h leq 2^{63}-1$
因为h过大所以直接建图显然是不行的,我们要优化空间。
我们因为这个跳的顺序是无关的,所以每个数都可以由若干次b/c再加上若干次a而形成的。
根据带余除法我们知道所有的整数数都可以写成ax+r的形式,其中a是除数,x是商而r是余数。
我们求出通过b/c操作能到达的最小的mod a余数是r的数,然后用一些算法即可求出能到达多少小于h的整数(到时再讲)。
这时我们同余最短路就该排上用场了。这个最小即可表示成最短路。
我们可以让a来做这个除数(其实应该用最小的最优),则r属于$[0,a-1]$。
我们要求出所有到达所有r的最小值。所以对于每个r建立一个点。
它可以通过b,c到其它的数(点),所以我们对于每个点u连一条到v=(u+(b/c))%a的边,长度为(b/c)。
现在从0开始跑最短路即可(初始化dis[0]=0)。
设余数r的最短路为dis[r],则可以到$frac{h-dis[r]}{a}+1$个整数,统计答案。
#include <bits/stdc++.h> using namespace std; const long long MAXA = 1e5 + 10; struct node{ long long pre, to, val; }edge[MAXA * 20]; long long head[MAXA], tot; long long n, h; long long a[20]; long long dis[MAXA], vis[MAXA]; queue<long long> q; void add(long long u, long long v, long long l) { edge[++tot] = node{head[u], v, l}; head[u] = tot; } void spfa() { memset(dis, 0x3f, sizeof(dis)); dis[0] = 0; vis[0] = 1; q.push(0); while (!q.empty()) { long long x = q.front(); q.pop(); for (long long i = head[x]; i; i = edge[i].pre) { long long y = edge[i].to; if (dis[y] > dis[x] + edge[i].val) { dis[y] = dis[x] + edge[i].val; if (!vis[y]) { vis[y] = 1; q.push(y); } } } vis[x] = 0; } } long long solve(long long x) { long long ret = 0; for (long long i = 0; i < a[1]; i++) { if (dis[i] <= x) { ret += (x - dis[i]) / a[1] + 1; } } return ret; } int main() { n = 3; cin >> h; for (long long i = 1; i <= n; i++) { cin >> a[i]; } for (long long i = 0; i < a[1]; i++) { for (long long j = 2; j <= n; j++) { add(i, (i + a[j]) % a[1], a[j]); } } spfa(); cout << solve(h - 1);//他刚开始在1楼所以要-1 return 0; }
习题:
以上是关于「学习笔记」同余最短路的主要内容,如果未能解决你的问题,请参考以下文章