同余最短路小记

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; 
		}

注意处理 \(a_i=0\)!!!!

注意处理 \(a_i=0\)!!!!

注意处理 \(a_i=0\)!!!!

posted @ 2026-01-15 08:20  Zwi  阅读(3)  评论(0)    收藏  举报