C. Distance Indicators

\(j-A_j = A_i+i\)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    map<int, int> cnt;
    rep(i, n) cnt[i+a[i]]++;
    
    ll ans = 0;
    rep(i, n) ans += cnt[i-a[i]];
    
    cout << ans << '\n';
    
    return 0;
}

D. Takahashi's Expectation

如果 \(x \leqslant 1000\),可以直接dp解决
dp[i][j] 表示从接收第 \(i\) 个礼物开始,当前情绪值为 \(j\) 时,向后接收所有礼物后的最终的情绪值,其中 \(j \leqslant 1000\)(因为 \(p_i\) 的上界是 \(500\))
需要从后往前进行转移
最后的答案为 dp[0][x]

如果 \(x > 1000\) 时应该如何处理?
容易发现,当 \(x \geqslant 500\) 时,情绪一定会一直减少,还是因为 \(p_i\) 的上界是 \(500\),所以只会触发减少的操作
\(S_i = b_0 + b_1 + \cdots + b_{i-1}\)
二分找到 \(S\) 中满足 \(x-S_i \leqslant 1000\) 的最小的 \(i\)
然后讨论一下:
如果 \(i < n\),答案为 dp[i][x-S[i]]
否则,答案为 x-S[i](已经收完了所有礼物)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> p(n), a(n), b(n);
    rep(i, n) cin >> p[i] >> a[i] >> b[i];
    
    const int m = 1001;
    vector dp(n+1, vector<int>(m));
    rep(i, m) dp[n][i] = i;
    for (int i = n-1; i >= 0; --i) {
        rep(j, m) {
            int nj;
            if (j <= p[i]) nj = j+a[i]; else nj = max(0, j-b[i]);
            dp[i][j] = dp[i+1][nj];
        }
    }
    
    vector<int> bs(n+1);
    rep(i, n) bs[i+1] = bs[i]+b[i];
    
    int q;
    cin >> q;
    rep(qi, q) {
        int x;
        cin >> x;
        int ans;
        if (x >= m) {
            int i = upper_bound(bs.begin(), bs.begin()+n, x-m)-bs.begin();
            x -= bs[i];
            if (i < n) ans = dp[i][x];
            else ans = x;
        }
        else ans = dp[0][x];
        cout << ans << '\n';
    }
    
    return 0;
}

E. A Path in A Dictionary

从点 \(x\) 出发,每次走到能走到点 \(y\) 的点中编号最小的相邻节点

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

void solve() {
    int n, m, x, y;
    cin >> n >> m >> x >> y;
    n++;
    
    vector<vector<int>> to(n);
    rep(i, m) {
        int a, b;
        cin >> a >> b;
        to[a].push_back(b);
        to[b].push_back(a);
    }
    
    vector<bool> used(n);
    vector<int> ans(1, x);
    used[x] = true;
    while (x != y) {
        vector<bool> reach(n);
        queue<int> q;
        reach[y] = true; q.push(y);
        while (q.size()) {
            int v = q.front(); q.pop();
            for (int u : to[v]) {
                if (used[u]) continue;
                if (reach[u]) continue;
                reach[u] = true;
                q.push(u);
            }
        }
        int nx = n;
        for (int v : to[x]) {
            if (used[v]) continue;
            if (!reach[v]) continue;
            nx = min(nx, v);
        }
        x = nx;
        used[x] = true;
        ans.push_back(x);
    }
    
    n = ans.size();
    rep(i, n) cout << ans[i] << " \n"[i == n-1];
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

F. Random Gathering

对于每次操作,区间 \([l, r]\) 上每个位置的石子数量的期望值都为 \(\dfrac{\sum\limits_{i=l}^r A_i}{r-l+1}\)

于是问题就变成一个“区间赋值,区间求和”的线段树问题了

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using mint = modint998244353;

struct S { int w; mint sum; };
S op(S a, S b) { return S(a.w+b.w, a.sum+b.sum); }
S e() { return S(0, 0); }

struct F { bool change; mint x; };
S mapping(F f, S x) {
    if (!f.change) return x;
    return S(x.w, f.x*x.w);
}
F composition(F f, F g) {
    if (!f.change) return g;
    return f;
}
F id() { return F(false, 0); }

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<S> a(n);
    rep(i, n) {
        int x;
        cin >> x;
        a[i] = S(1, x);
    }
    
    lazy_segtree<S, op, e, F, mapping, composition, id> t(a);
    rep(mi, m) {
        int l, r;
        cin >> l >> r;
        --l;
        S s = t.prod(l, r);
        mint ave = s.sum/s.w;
        t.apply(l, r, F(true, ave));
    }
    
    rep(i, n) cout << t.get(i).sum.val() << ' ';
    
    return 0;
}

G. Binary Cat

启发式合并
考虑从后往前算答案
每次维护一个集合,代表后面那些串要在这里算答案,并且要求第多少位
然后就可以把在 \(l\) 的和在 \(r\) 的分裂出去

另一种做法是倍增
虽然可以像ABC380D那样处理,但如果每个查询问的是“将 \(S_i\)\(S_0\) 拼接后,第 \(1\) 个字符是什么?”,时间复杂度会达到 \(O(Q^2)\)
然而,如果像ABC380D那样字符串长度每次减半,就能在 \(O(\log N)\) 时间内解决。因此,我们只需通过倍增预处理,将“\(S_{L_i}\)\(S_{R_i}\) 规约到较长字符串的情况”统一处理,就能高效回溯查询结果!