C. Rotate and Sum Query

如果没有第一种查询,只要直接做前缀和就行。
即使有第一种查询,也不必真的去移动元素,只要把下标整体偏移一下,记住“原序列中每个元素现在排在第几位”,就能知道想要的区间和在原序列的哪一段!

代码实现
#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, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<ll> s(n+1);
    rep(i, n) s[i+1] = s[i]+a[i];
    
    int si = 0;
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            int c;
            cin >> c;
            si = (si+c)%n;
        }
        else {
            int l, r;
            cin >> l >> r;
            --l; --r;
            l = (l+si)%n;
            r = (r+si)%n;
            ll ans;
            if (l <= r) ans = s[r+1]-s[l];
            else ans = s[n] - (s[l]-s[r+1]);
            cout << ans << '\n';
        }
    }
    
    return 0;
}

D. Ulam-Warburton Automaton

模拟
黑格子保持黑色,只有与“上一轮操作中新变黑的格子”相邻的白色格子,才可能在这一轮变成黑色,所以每次只要检查这些格子就行!而上一轮操作中新变黑的格子总数不超过 \(HW\) 个,因此整体时间复杂度为 \(O(HW)\)

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

using namespace std;
using P = pair<int, int>;

const int di[] = {-1, 0, 1, 0};
const int dj[] = {0, 1, 0, -1};

int main() {
    int h, w;
    cin >> h >> w;
    
    vector<string> s(h);
    rep(i, h) cin >> s[i];
    
    const int INF = 1001001001;
    vector step(h, vector<int>(w, INF));
    queue<P> q;
    rep(i, h)rep(j, w) if (s[i][j] == '#') {
        step[i][j] = 0;
        q.emplace(i, j);
    }
    
    int ans = 0;
    while (q.size()) {
        ans++;
        auto [i, j] = q.front(); q.pop();
        int si = step[i][j];
        rep(v, 4) {
            int ni = i+di[v], nj = j+dj[v];
            if (ni < 0 or ni >= h or nj < 0 or nj >= w) continue;
            if (step[ni][nj] != INF) continue;
            int cnt = 0;
            rep(v2, 4) {
                int mi = ni+di[v2], mj = nj+dj[v2];
                if (mi < 0 or mi >= h or mj < 0 or mj >= w) continue;
                if (step[mi][mj] <= si) cnt++;
            }
            if (cnt == 1) {
                step[ni][nj] = si+1;
                q.emplace(ni, nj);
            }
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}
代码实现2
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using P = pair<int, int>;

const int di[] = {-1, 0, 1, 0};
const int dj[] = {0, 1, 0, -1};

int main() {
    int h, w;
    cin >> h >> w;
    
    vector<string> s(h);
    rep(i, h) cin >> s[i];
    
    vector<P> updatedCells;
    rep(i, h)rep(j, w) if (s[i][j] == '#') {
        updatedCells.emplace_back(i, j);
    }
    
    int ans = 0;
    while (updatedCells.size()) {
        ans += updatedCells.size();
        vector<P> nextCells;
        for (auto [i, j] : updatedCells) {
            rep(v, 4) {
                int ni = i+di[v], nj = j+dj[v];
                if (ni < 0 or ni >= h or nj < 0 or nj >= w) continue;
                if (s[ni][nj] == '#') continue;
                int cnt = 0;
                rep(v2, 4) {
                    int mi = ni+di[v2], mj = nj+dj[v2];
                    if (mi < 0 or mi >= h or mj < 0 or mj >= w) continue;
                    if (s[mi][mj] == '#') cnt++;
                }
                if (cnt == 1) {
                    nextCells.emplace_back(ni, nj);
                }
            }
        }
        for (auto [i, j] : nextCells) {
            s[i][j] = '#';
        }
        swap(updatedCells, nextCells);
    }
    
    cout << ans << '\n';
    
    return 0;
}

E. Count Sequences 2

答案 \(= \dbinom{C_1}{C_1} \times \dbinom{C_1+C_2}{C_2} \times \dbinom{C_1+C_2+C_3}{C_3} \times \cdots\)
用杨辉三角预处理组合数即可

代码实现
#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 = modint;

const int M = 5000;
mint comb[M+1][M+1];

void solve() {
    int n;
    cin >> n;
    
    int s = 0;
    mint ans = 1;
    rep(i, n) {
        int c;
        cin >> c;
        s += c;
        ans *= comb[s][c];
    }
    
    cout << ans.val() << '\n';
}

int main() {
    int t, mod;
    cin >> t >> mod;
    mint::set_mod(mod);
    
    comb[0][0] = 1;
    rep(i, M)rep(j, i+1) {
        comb[i+1][j] += comb[i][j];
        comb[i+1][j+1] += comb[i][j];
    }
    
    while (t--) solve();
    
    return 0;
}

F. Inserting Process

首先,这是一个统计逐字符删除操作序列数量的问题。删除方式不同却得到相同字符串,仅在连续相同字符区间删除位置不同才会发生。因此,把“在连续相同字符区间内只能选最前面的字符”作为条件,然后以当前剩余字符作为状态做状压dp即可!

代码实现
#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;

int main() {
    int n;
    string t;
    cin >> n >> t;
    int n2 = 1<<n;
    
    vector<mint> dp(n2);
    dp[n2-1] = 1;
    for (int s = n2-1; s; --s) {
        char pre = '-';
        rep(i, n) if (s>>i&1) {
            if (pre != t[i]) dp[s^1<<i] += dp[s];
            pre = t[i];
        }
    }
    
    mint ans = dp[0];
    cout << ans.val() << '\n';
    
    return 0;
}

G. Sum of Min of XOR

对于单个 \(x\),通过 \(01 \mathrm{Trie}\) 可以用贪心法求最小异或值:从最高位向下走,若存在与 \(x\) 当前位相同的子节点就走那边(使当前位的 \(XOR = 0\)),否则只能走相反位(当前位的 \(XOR = 1\),贡献 \(2^i\)),继续处理下一位。最终得到的路径对应的数就是 \(\min_i (x\oplus A_i\))。

把“对每个 \(x\)\(01 \mathrm{Trie}\) 上贪心求最小 \(XOR\)”的操作并行化:按位从高到低把区间 \([0,M)\) 划分成 \(2^i\) 对齐的块,用状态 (trie-node, block-length) 表示“在当前高位信息下剩余要处理的连续 \(x\)”,若某一位对于该状态必然产生 \(1\) 则直接把该位贡献累加并把这些 \(x\) 送到相反子树继续处理,否则把它们送到存在的同位子树。

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

using namespace std;
using ll = long long;
using P = pair<int, int>;

const int K = 30;
struct BinaryTrie {
    struct Node { int l, r; };
    vector<Node> nodes;
    BinaryTrie(): nodes(1, Node(-1, -1)) {}
    int getNode(int& v, bool create=false) {
        if (!create or v != -1) return v;
        int res = (v = nodes.size());
        nodes.emplace_back(-1, -1);
        return res;
    }
    int go(int v, int d, bool create=false) {
        if (d == 0) return getNode(nodes[v].l, create);
        else return getNode(nodes[v].r, create);
    }
    void add(int x) {
        int v = 0;
        for (int i = K-1; i >= 0; --i) {
            v = go(v, x>>i&1, true);
        }
    }
};

int main() {
    int n, m;
    cin >> n >> m;
    
    BinaryTrie t;
    rep(i, n) {
        int a;
        cin >> a;
        t.add(a);
    }
    
    ll ans = 0;
    map<P, ll> d;
    d[P(0, m)] = 1;
    for (int i = K-1; i >= 0; --i) {
        int b = 1<<i;
        map<P, ll> old;
        swap(d, old);
        for (auto [p, x] : old) {
            auto [v, r] = p;
            int r0 = min(r, b);
            int r1 = r-b;
            int v0 = t.go(v, 0);
            int v1 = t.go(v, 1);
            {
                if (v0 == -1) {
                    ans += (ll)r0*b*x;
                    d[P(v1, r0)] += x;
                }
                else {
                    d[P(v0, r0)] += x;
                }
            }
            if (r1 > 0) {
                if (v1 == -1) {
                    ans += (ll)r1*b*x;
                    d[P(v0, r1)] += x;
                }
                else {
                    d[P(v1, r1)] += x;
                }
            }
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}