学习自 Alex_Wei 的博客。
同余最短路模板题:[国家集训队] 墨墨的等式。
已知长为 \(n\) 的序列 \(a\)。对于不定方程 \(\sum\limits_{i=1}^na_ix_i=b\;(x_i\ge0)\),问有多少 \(b\in[l,r]\) 可以使得方程有解。
\(n\le12\),\(a_i\le5\times10^5\),\(l,r\le10^{12}\)。
本文默认取模得到的结果都是自然数。
首先把询问拆成区间 \([0,l-1]\) 和 \([0,r]\),只考虑求区间 \([0,l]\) 的合法数。
设集合 \(B=\{x\in\mathbb Z\mid 当\,b=x\,时原方程有解\}\)。显然如果 \(r\in B\) ,那么 \(r+a_i\in B\)。
设集合 \(\mathrm Z_p^i=\{x\in \mathbb{Z}\mid x\equiv i\pmod p\}\),即模 \(p\) 余 \(i\) 的剩余类。显然有结论:
\[i\equiv j\!\!\!\pmod p\iff \mathrm Z_p^i=\mathrm Z_p^j \]\[\bigcup\limits_{i=k}^{k+p-1}\mathrm Z_p^i=\mathbb Z \]我们随便选一个 \(a_i\),方便起见我们选择 \(a_1\)。设 \(f(i)\) 表示 \(\mathrm Z_{a_1}^i\cap B\) 的最小元素,方便起见令 \(i\in[0,a_1)\)。
显然 \(\forall k\ge 0,f(i)+ka_1\in\mathrm Z_{a_1}^i\cap B\)。因此如果我们求出了 \(f(i)\),对于每个 \(i\) 我们都可以算出满足 \(Z_{a_1}^i\cap B\cap[0,l]\) 的大小,即模 \(a_1\) 余 \(i\) 的答案数。因为 \(\bigcup\limits_{i=0}^{a_1-1}\mathrm Z_{a_1}^i=\mathbb Z\),所以这些答案加起来不重不漏。
考虑如何求 \(f_i\),不难发现:
\[f(0)=0 \]\[f(i)=\min_{j=2}^nf((i-a_j)\bmod a_1)+a_j \]一种方法是建图,用最短路解决。点数为 \(a_i\),边数为 \(na_i\),时间复杂度是 Dijkstra 的 \(O(na_1\log na_1)\) 或 SPFA 的 \(O(na_1^2)\)。
但是还有另一种方式,考虑逐个添加 \(a\),设 \(g(j,i)\) 表示只有前 \(j\) 个数时的 \(f(i)\),现在考虑从 \(j-1\) 到 \(j\),添加一个 \(a_j\) 会发生的转移:
\[g(j-1,i)+ka_j\rightarrow g(j,(i+ka_j)\bmod a_1)\quad(k\ge0) \](箭头表示“更新”)
对于每个 \(i\),考虑从 \(0\) 开始枚举 \(k\),不难发现 \(k=\dfrac{a_1}{\gcd(a_1,a_j)}\) 时就可以停下了,因为此时 \(i+ka_j\equiv i\pmod{a_1}\),再往下走只会把之前更新过的再更新一遍,但是由于此时的 \(k\) 比上一次更新时大,所以这些更新不会再起效果。
由于模数相同时,不同的剩余类交集为空,所以考虑对于每个剩余类中的下标分别更新。形式化地说,就是对每个 \(x\in[0,a_j)\),分别更新 \(f_i\),其中 \(i\in\mathrm Z_{a_j}^x\)。从完全背包的角度考虑这个问题,压掉第一维,得到转移 \(f(i)+a_j\rightarrow f((i+a_j)\bmod a_{1})\)。从 \(i\) 开始走 \(k=\dfrac{a_1}{\gcd(a_1,a_j)}\) 步可以更新完 \(i\) 能更新的点,那么从 \(i\) 开始走 \(2k\) 步,也就是“多走一圈”,就相当于从 \(i\) 所在的剩余类中的每个点都走了 \(k\) 步,也就可以更新完这个剩余类中的每个点。
for(int p = i, c = 0; c < 2; c += p == i) {
gmin(f[(p + a[j]) % a[1]], f[p] + a[j]);
p = (p + a[j]) % a[1];
}
每次可以更新 \(\dfrac{a_1}{\gcd(a_1,a_j)}\) 个位置,所以只需要枚举前 \(\gcd(a_1,a_j)\) 个位置进行更新即可。
时间复杂度 \(\Theta(na_1)\)。可以先排序让 \(a_1\) 最小,减小常数。
#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define int long long
#define gmin(x, y) (x = min(x, y))
using namespace std;
constexpr int N = 15, A = 5e5;
int n, a[N], l, r, f[A];
signed main() {
#ifdef ONLINE_JUDGE
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n >> l >> r;
rep(i, 1, n) cin >> a[i];
sort(a + 1, a + n + 1);
memset(f, 0x3f, sizeof f);
f[0] = 0;
rep(j, 2, n) rep(i, 0, __gcd(a[1], a[j]) - 1)
for(int p = i, c = 0; c < 2; c += p == i) {
gmin(f[(p + a[j]) % a[1]], f[p] + a[j]);
p = (p + a[j]) % a[1];
}
int ans = 0;
--l;
rep(i, 0, a[1] - 1) {
if(l >= f[i]) ans -= (l - f[i]) / a[1] + 1;
if(r >= f[i]) ans += (r - f[i]) / a[1] + 1;
}
cout << ans << endl;
return 0;
}
标签:gcd,na,短路,cap,更新,转圈,同余,rep,mathrm
From: https://www.cnblogs.com/untitled0/p/tyzdl.html