数据结构思维题选做(长期更新)

到处乱找的. 用到的数据结构在 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;
}
posted @ 2025-09-22 21:07  Ydoc770  阅读(15)  评论(0)    收藏  举报