C. 403 Forbidden

对于询问1,可以维护一个set来记录到目前为止出现的所有 (x, y) 二元组
对于询问2,可以开一个状态数组来记录第 \(x\) 个人是否可以访问所有网页

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

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

int main() {
    int n, m, q;
    cin >> n >> m >> q;
    
    vector<bool> all(n+1);
    set<P> privs;
    rep(qi, q) {
        int type, x;
        cin >> type >> x;
        if (type == 2) {
            all[x] = true;
        }
        else {
            int y;
            cin >> y;
            if (type == 1) {
                privs.emplace(x, y);
            }
            if (type == 3) {
                if (all[x] or privs.count(P(x, y))) puts("Yes");
                else puts("No");
            }
        }
    }
    
    return 0;
}

D. Forbidden Difference

序列的排列顺序并不重要,可以按值模 \(D\) 的余数分别考虑
这样一来,例如,如果新建一个表示 \(A\)\(D, 2D, 3D, 4D, \cdots\) 的个数的序列,问题就变成了“当不能同时选择相邻元素时,和的最大值是多少”
这个问题是经典的链的独立集的问题

dp[i][0/1] 表示从前 \(i\) 个数中选若干个数且最后一个数选或不选时的最大独立集

还需要特判一下 \(D=0\) 这种情况

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

using namespace std;

int solve(vector<int> a) {
    int n = a.size();
    vector dp(n+1, vector<int>(2));
    rep(i, n) {
        dp[i+1][1] = max(dp[i][0], dp[i][1]);
        dp[i+1][0] = dp[i][1]+a[i];
    }
    int mx = max(dp[n][0], dp[n][1]);
    int sum = 0;
    rep(i, n) sum += a[i];
    return sum-mx;
}

int main() {
    int n, d;
    cin >> n >> d;
    
    const int M = 1000005;
    vector<int> cnt(M);
    rep(i, n) {
        int a;
        cin >> a;
        cnt[a]++;
    }
    
    int ans = 0;
    if (d == 0) {
        rep(i, M) {
            if (cnt[i] > 0) ans += cnt[i]-1;
        }
    }
    else {
        rep(si, d) {
            vector<int> a;
            for (int i = si; i < M; i += d) {
                a.push_back(cnt[i]);
            }
            ans += solve(a);
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}

E. Forbidden Prefix

先将所有字符串插到 \(\operatorname{Trie}\) 树上
对于 \(\operatorname{Trie}\) 树上每个点维护它是否存在,对于 \(T_i=1\) 这个操作,暴力地将这个字符串在 \(\operatorname{Trie}\) 树上对应的点 \(v\) 的子树上的所有点删掉(特别地,删过一次的点,以它为根的子树就不用再删了,所以复杂度是正确的),同时将答案减去当前点对应的 \(Y\) 串个数
对于 \(T_i=2\) 这个操作,对答案加 \(1\),并将这个字符串对应的点所在的 \(Y\) 串的个数加 \(1\)

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

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

struct Trie {
    using MP = map<char, int>;
    vector<MP> to;
    Trie(): to(1) {}
    int add(const string& s) {
        int v = 0;
        for (char c : s) {
            if (to[v].count(c) == 0) {
                int u = to.size();
                to[v][c] = u;
                to.push_back(MP());
            }
            v = to[v][c];
        }
        return v;
    }
    
    int ans;
    vector<bool> ng;
    vector<int> num_y;
    void init() {
        ans = 0;
        int n = to.size();
        ng.resize(n);
        num_y.resize(n);
    }
    void addx(int v) {
        if (ng[v]) return;
        ng[v] = true;
        ans -= num_y[v];
        for (auto [c, u] : to[v]) addx(u);
    }
    void addy(int v) {
        if (ng[v]) return;
        ans++;
        num_y[v]++;
    }
};

int main() {
    int q;
    cin >> q;
    
    Trie t;
    vector<P> qs;
    rep(qi, q) {
        int type;
        string s;
        cin >> type >> s;
        int v = t.add(s);
        qs.emplace_back(type, v);
    }
    t.init();
    
    for (auto [type, v] : qs) {
        if (type == 1) t.addx(v); else t.addy(v);
        cout << t.ans << '\n';
    }
    
    return 0;
}

F. Shortest One Formula

dp
只需按值从小到大的顺序,求出长度最小的 exprtermfactor即可!

代码实现
#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<string> dp_expr(n+1);
    vector<string> dp_term(n+1);
    vector<string> dp_factor(n+1);
    auto chmin = [&](string& s, const string& t) {
        if (t == "") return;
        if (s == "" or t.size() < s.size()) s = t;
    };
    
    for (int i = 1; i <= n; i = i*10+1) dp_factor[i] = to_string(i);
    
    for (int i = 1; i <= n; ++i) {
        rep(k, 2) {
            chmin(dp_expr[i], dp_term[i]);
            for (int j = 1; j < i; ++j) {
                if (dp_expr[j] == "") continue;
                if (dp_term[i-j] == "") continue;
                chmin(dp_expr[i], dp_expr[j]+"+"+dp_term[i-j]);
            }
            chmin(dp_term[i], dp_factor[i]);
            for (int j = 1; j <= i; ++j) {
                if (i%j) continue;
                if (dp_term[j] == "") continue;
                if (dp_factor[i/j] == "") continue;
                chmin(dp_term[i], dp_term[j]+"*"+dp_factor[i/j]);
            }
            if (dp_expr[i] != "") chmin(dp_factor[i], "("+dp_expr[i]+")");
        }
    }
    
    cout << dp_expr[n] <<'\n';
    
    return 0;
}

G. Odd Position Sum Query

动态开点线段树或平衡树

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

using namespace std;
using ll = long long;

struct S {
    int cnt;
    ll sum0, sum1;
    S(): cnt(0), sum0(0), sum1(0) {}
    S(int x): cnt(1), sum0(x), sum1(0) {}
    S& operator+=(const S& s) {
        if (cnt&1) {
            sum0 += s.sum1; sum1 += s.sum0;
        }
        else {
            sum0 += s.sum0; sum1 += s.sum1;
        }
        cnt += s.cnt;
        return *this;
    }
    S operator+(const S& s) const { return S(*this)+=s; }
};

struct segtree {
    int n;
    unordered_map<int, S> d;
    segtree(int mx=0) {
        n = 1;
        while (n < mx) n <<= 1;
    }
    void add(int i, S s) {
        i += n;
        d[i] += s;
        while (i > 1) {
            i >>= 1;
            int l = i<<1, r = l|1;
            d[i] = d[l]+d[r];
        }
    }
    S get() { return d[1]; }
};

int main() {
    int q;
    cin >> q;
    
    const ll mod = 1e9;
    segtree t(mod);
    ll z = 0;
    rep(qi, q) {
        ll y;
        cin >> y;
        ll x = (y+z)%mod + 1;
        t.add(x, S(x));
        z = t.get().sum0;
        cout << z << '\n';
    }
    
    return 0;
}