同余最短路
- 题型大概为若干个无限多个正整数来拼凑出某些数(类似完全背包)。
- 套路为随便选一个数 \(M\) 转化为求 \(f(x)\) 表示拼凑出 \(x + kM\) 的最小 \(k\)。转化为最短路。
- 该模型的强大之处在于将完全背包的结构清晰地展现出来。
同余最短路求能凑成多少(H以内的)数
经典题目:跳楼机
随便挑一个数 \(M\) 作为模数,求出上述 \(f(x)\) 即可算出答案。
inline void dij() {
...
while (!q.empty()) {
...
to = (cur + y) % x;
if (vis[to]) continue;
if (dis[cur] + y < dis[to]) {
dis[to] = dis[cur] + y;
q.push((node){dis[to], to});
}
...
}
}
同余最短路求最大不能拼凑出来的数
“小凯的疑惑”加强版
经典题目:牛场围栏
特判掉存在 \(1\) 的情况和 gcd 不为 1 的情况。
随便找一个数(最小的数)当作模数,求出 \(f\) 后根据 \(f\) 求解。
绕圈法
我退役后才注意到的新做法。将同余最短路看作完全背包问题,最多加 \(\frac{m}{gcd(m,v)}\) 个 \(v\),否则可以被 \(m\) 替换掉。因此可以对 \(Z_m\) 上由加 \(v\) 得到的 \(gcd(m, v)\) 个轨道分别顺序递推(例如对每个轨道找到最小值,从这里开始往后更新;或者直接推两遍)。复杂度是优秀的 \(O(nm)\) 且极其好写。
memset(f, 0x3f, sizeof(f));
f[0] = 0;
for (int i = 2; i <= n; ++i) {
int v = a[i], g = get_gcd(a[1], a[i]);
for (int j = 0; j < g; ++j) {
int p = j;
do {
MIN(f[(p+v)%a[1]], f[p]+v);
p = (p+v)%a[1];
} while (p != j);
do {
MIN(f[(p+v)%a[1]], f[p]+v);
p = (p+v)%a[1];
} while (p != j);
}
}
一类完全背包问题
题意:
本题中,你需要解决完全背包问题。
有 \(n\) 种物品,第 \(i\) 种物品单个体积为 \(v_i\)、价值为 \(c_i\)。
\(q\) 次询问,每次给出背包的容积 \(V\),你需要选择若干个物品,每种物品可以选择任意多个(也可以不选),在选出物品的体积的和恰好为 \(V\) 的前提下最大化选出物品的价值的和。你需要给出这个最大的价值和,或报告不存在体积和恰好为 \(V\) 的方案。
为了体现你解决 NP-Hard 问题的能力,\(V\) 会远大于 \(v_i\),详见数据范围部分。
\(1 \le n \le 50, 1 \le v_i \le 10^5, 1 \le c_i \le 10^6, 1 \le q \le 10^5, 10^{11} \le V \le 10^{12}\)
题解:
直观想法是尽可能多去选性价比最高的物品(设其为 \((m, w)\)),问题是怎么样尽早达成余数要求,并且这里未必“越早越好”,还需要考虑达成余数要求时所产生的那部分额外收益。
考虑模 \(m\) 下跑同余最短路时,对于两个余数相同的状态 \((V_1, C_1)\) 和 \((V_2, C_2)\),假设后来有个询问 \(V\) 满足 \(V \mod m = V_1 \mod m(= V_2 \mod m)\),我们将会选择 \(V_i \le V\) 且 \(C_i + \frac{V-V_i}{m}w\) 最大的状态。因此可以把 \(C_i-\lfloor \frac{V_i}{m}\rfloor w\) 作为状态的权值来比较更新。(后面会证明这样得到的答案必定满足 \(V_i \le V\))
基于上述思想,同余最短(长)路转移时可以通过加入 \((v_i,c_i)\) 物品由 \((V^p, C^p)\) 转移到 \((C^q,V^q), q = (p+v_i)\mod m\),权值更新: f[q]=max(f[q], f[p]+c[i]-(p+v)/m*w)
,即跑最长路。因为 \((m,w)\) 性价比最高,所以图无正环。
因为无正环,所以最长路无环,所以最多 \(m\) 条边,即最多用了 \(m\) 个物品,因此最多体积 \(m \times maxv\),即 \(maxv^2 \le 10^{10} < 10^{11}\),所以这样得到的答案必定满足 \(V_i \le V\)。
单调队列优化等差数列同余最短路
使用字符串 \(S\) 的所有 border 长度能拼出来 \(w\) 以内的多少个数?\(|S| \le 5 \times 10^5, w \le 10^{18}\)
border 长度集合可以划分为不超过 \(\log_2 |S|\) 个等差数列。对于等差数列 \(x, x+d, x+2d, \dots, x+ld\),在 \(\mod x\) 下跑同余最短路,则转移的时候是在每个轨道上用长度为 \(l\) 滑动窗口取最小值来转移。如何从模 \(p\) 切换到模 \(q\)?注意到我们处理出来的模 \(p\) 的结果告诉我们长度集合 \(\{dis_i + kp | 0 \le i \lt p, k \ge 0\}\) 是可以达到的,因此在模 \(q\) 下我们可以先把 \(dis_i\) 都加上去,然后再用 \(p\) 转移一遍。
当然还有一些奇奇怪怪的同余最短路题
Small Multiple
给定 \(n\),求 \(n\) 的倍数中,数位和最小的那一个数的数位和。
\(1\le n\le 10^5\)
Small Multiple 题解
- 每个十进制数都可以由“乘十”和“加一”来表示,其中“加一”的次数即为数位和。
- 因为要求 \(n\) 的倍数,即模 \(n\) 为0,因此考虑在模 \(n\) 下的从 \(1\) 到 \(0\) 的最短路。