同余最短路小记
posted on 2025-02-24 11:20:47 | under | source
介绍
用于解决类似下列问题:
- 给定 \(a_1\dots a_n\),求能凑出多少整数 \(\in [0,r]\)。
(当然变式很多,只是做例子。)
一般来说,能够使用同余最短路需要满足 \(a\) 中有较小的元素。
算法
选取一个 \(a\) 做基准元素,下文称它为 \(m\)。
记 \(f_i\) 为可以凑出来的且模 \(m\) 为 \(i\) 的最小整数,这样记是因为能凑出 \(i\) 就有 \(i+km\) 能凑。
只需求出 \(f\) 即可,\(ans=\sum \lfloor \frac{r-f_i}m\rfloor+1\)。
有转移 \(f_i+a_j\to f_{(i+a_j)\bmod m}\)。
显然可以最短路优化,做到 \(O(mn\log m)\)。
转圈技巧
来自 Alex_Wei 的博客。
考虑逐步加入 \(a_i\)。那么相当于新增了一些边,那么从 \(x\) 走 \(\frac {lcm(m,a_i)}{a_i}=\frac m{gcd(m,a_i)}\) 次 \(a_i\) 边就会回到 \(x\),所以得到了 \(gcd(m,a_i)\) 个环。因为是最短路,所以不会经过重复点,否则就有负环了(仅限同余最短路)。那么对于一个环,只需“转”两圈即可考虑到所有转移。“转”指通过 \(a_i\) 边转移一次。
这样复杂度就是 \(O(mn)\) 的,而且极为好写。
模版:
for(int i = 1; i <= n; ++i)
for(int j = 0, lim = __gcd(a[i], mod); j < lim; ++j)
for(int k = j, c = 0; c < 2; c += (k == j)){
int nxt = (k + a[i]) % mod;
MIN(f[nxt], f[k] + a[i]);
k = nxt;
}

浙公网安备 33010602011771号