到处乱找的. 用到的数据结构在 NOIP 考纲内,主要是学习、锻炼各种处理思路. 代码的实现都不算困难.
倍增思想
P10198 [USACO24FEB] Infinite Adventure P
Hint:跳的步数明显提示倍增. 根据 \(\sum T_i\) 的限制直接预处理是三只 \(\log\) 的,考虑怎样优化到两只 \(\log\).
的确是非常深刻的倍增题.
设 \(f_{u,k,t}\) 表示在日期 \(\bmod T_u=t\) 从 \(u\) 开始跳 \(2^k\) 步到达的节点. 但是这样直接做正确性无法保证,因为可能跳到 $ T_v>T_u$ 的节点 \(v\),这时我们就无法继续倍增了. 根据 \(\sum T_i\le10^5\) 以及 \(T_i\) 是 \(2\) 的幂,令 \(V=\sum T_i\),所以只存在 \(O(\log V)\) 种不同的 \(T_i\). 于是我们令出现上述情况时 \(u\) 停在 \(v\) 不再继续跳,回答单次询问就是 \(O(\log V\log\Delta)\) 的. 由于预处理要对每种 \(T_i\) 都做一遍,所以总复杂度是 \(O(V\log^2V\log \Delta+q\log V\log\Delta)\) 的.
上述的预处理方法实际上有大量冗余的部分,如果跳到一个 \(T_v<T_u\) 的节点那么我们会丢失 \(u\) 的信息,之后再扩大 \(T_u\) 范围时间复杂度很劣. 因为 \(T_u\) 本身其实可以记录所有 \(\le T_u\) 的状态,所以我们可以通过修改倍增状态 \(f_{u,k,t}\) 的定义为跳 \(2^k\) 个 \(T_v=T_u\) 的节点后到达的点,此时转移就需要额外记录跳的步数 \(d_{u,k,t}\). 我们仍然强制让 \(f\) 停在第一个 \(T_v>T_u\) 的 \(v\),就可以先通过每次跳极大的步数来用 \(O(\log V)\) 的时间找到第一个 \(T_v\ge T_u\) 的点作为 \(f_{u,0,t}\),并记下当前花的步数 \(d_{u,0,t}\),就可以倍增转移了. 这样大大优化了预处理的复杂度,总时间复杂度变为 \(O(V(\log V+\log \Delta)+q\log V\log\Delta)\).
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 1e5 + 10; const ll inff = 2e18;
int n, q, a[maxn];vector<int> b[maxn], f[maxn][70];
vector<ll> d[maxn][70];void init() {for(int s = 1; s < maxn; s <<= 1) {for(int u = 1; u <= n; u++) if(a[u] == s) {for(int i = 0; i < s; i++) {int v = b[u][i]; ll t = 1;while(a[v] < s && t < inff) {ll w = f[v][60][(i + t) % a[v]];t += d[v][60][(i + t) % a[v]], v = w;}f[u][0][i] = v, d[u][0][i] = min(t, inff);}}for(int k = 1; k <= 60; k++) for(int u = 1; u <= n; u++) if(a[u] == s) {for(int i = 0; i < s; i++) {int v = f[u][k - 1][i]; ll t = d[u][k - 1][i];if(a[v] == s) {f[u][k][i] = f[v][k - 1][(i + t) % s];d[u][k][i] = min(d[v][k - 1][(i + t) % s] + t, inff);}else f[u][k][i] = v, d[u][k][i] = t;}}}
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> n >> q;for(int i = 1; i <= n; i++) cin >> a[i];for(int i = 1; i <= n; i++) {b[i].resize(a[i]);for(int &p : b[i]) cin >> p;for(int k = 0; k <= 60; k++) d[i][k].resize(a[i]), f[i][k].resize(a[i]);}init();while(q--) {int u; ll cur, dis;cin >> u >> cur >> dis;while(dis) {for(int k = 60; ~k; k--) if(dis >= d[u][k][cur % a[u]]) {int v = f[u][k][cur % a[u]]; ll t = d[u][k][cur % a[u]];if(a[v] != a[u]) k = 61;dis -= t, cur += t, u = v;}if(dis) u = b[u][cur % a[u]], dis--, cur++;}cout << u << endl;}return 0;
}