T1. 乾坤

容易有一个贪心,按费用从小到大使用符咒,符咒去消灭能消灭的能力值最大的妖怪这显然是对的。可以用小根堆来实现。

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

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);

    int n, m;
    cin >> n >> m;

    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    sort(a.begin(), a.end(), greater<int>());

    vector<P> spells(m);
    rep(i, m) {
        int d, c;
        cin >> d >> c;
        spells[i] = {d, c};
    }

    sort(spells.begin(), spells.end(), [](const P& a, const P& b) {
        if (a.first == b.first) {
            return a.second < b.second;
        }
        return a.first > b.first;
    });

    int j = 0;
    ll ans = 0;
    priority_queue<int, vector<int>, greater<int>> q;
    for (int x : a) {
        while (j < m and spells[j].first >= x) {
            q.push(spells[j].second);
            j++;
        }
        if (!q.size()) {
            cout << "loss\n";
            return 0;
        }
        ans += q.top(); q.pop();
    }

    cout << ans << "\n";
    
    return 0;
}

T2. 赛道长度

希望暴力地对每个点维护 std::bitset 来统计以其为起点的每个长度是否出现过,只考虑子树 \(S_v\) 里的贡献是简单的,这个 bitset 记为 dp[v],接下来需要统计子树外的点 \(V \backslash S_v\) 中的贡献,这个是从父亲的 \(dp\) 中“挖掉” \(S_v\) 得到的,实际上做不到挖掉这个操作,于是在父亲处把统计 \(dp\) 的贡献转为维护一下前后缀或就可以了。时空复杂度均为 \(O(\frac{n^2}{w})\),可以通过。

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

using namespace std;
using ll = long long;

const int N = 50005, M = 26000;

int a[N];
vector<int> to[N];
bitset<M> dp[N], pre[N], suf[N], bs[N], tbs[N];
int ans[N];

void dfs(int v, int p=-1) {
    if (a[v]) dp[v].set(0);
    for (int u : to[v]) {
        if (u == p) continue;
        dfs(u, v);
        dp[v] |= dp[u]<<1;
    }
}

void dfs2(int v, int p=-1) {
    if (a[v]) bs[v].set(0);
    
    int tot = 0;
    vector<int> vc(1, -1);
    pre[0].reset();
    
    for (int u : to[v]) {
        if (u == p) continue;
        ++tot;
        vc.push_back(u);
        pre[tot] = pre[tot-1] | dp[u]<<2;
    }
    
    suf[tot+1].reset();
    for (int i = tot; i; --i) {
        suf[i] = suf[i+1] | dp[vc[i]]<<2;
    }
    
    for (int i = 1; i <= tot; ++i) {
        tbs[vc[i]] = pre[i-1] | suf[i+1];
    }
    
    for (int i = 1; i <= tot; ++i) {
        int u = vc[i];
        ans[u] = (bs[v]<<1 | tbs[u] | dp[u]).count();
        bs[u] = bs[v]<<1 | tbs[u];
        dfs2(u, v);
    }
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n;
    cin >> n;
    
    rep(i, n) cin >> a[i];
    
    rep(i, n-1) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        to[u].push_back(v);
        to[v].push_back(u);
    }
    
    dfs(0);
    ans[0] = dp[0].count();
    dfs2(0);
    
    rep(i, n) cout << ans[i] << '\n';
    
    return 0;
}

T3. 前缀最值

先忽略只确定了前 \(n\) 项的问题,考虑这个前缀最大值数组 \(f(q)\),由于其总是有序的,所以 \(f(q_1) = f(q_2)\) 当且仅当 \(f(q_1)\)\(f(q_2)\) 对应的桶相等,记这个桶为 \(c\)\(c_i\) 表示 \(i\) 出现在前缀最大值数组中的次数),则我们有 \(c_i \geqslant 0\)\(c_1 + c_2 + \cdots + c_i \leqslant i\)\(c_1 + c_2 + \cdots + c_{2n} = 2n\)
只要给出满足条件的 \(c\),我们总能找到 \(q\) 使得 \(f(q)\) 的桶是 \(c\),于是就可以对 \(c\) 进行折线计数了,具体如下:
\((0, 0)\) 出发,对于每个 \(1 \leqslant i \leqslant 2n\),向右移动一步,向上移动 \(c_i\) 步,且始终不越过 \(y=x\),最终到达 \((2n, 2n)\) 的方案数。
现在再加入确定了前 \(n\) 项的限制,我们变成了从 \((m, n)\) 出发,其中 \(m=\max(p_1, p_2, p_n)\)。不考虑不能越过 \(y=x\) 的限制的方案数是 \(\binom{3n-m}{n}\),越过的总能在第一次越过时沿着 \(y=x\) 把之后的路线全部翻折,等价于从 \((n-1, m+1)\) 出发的所有到达 \((2n, 2n)\) 的无约束路径条数,于是减去这样的 \(\binom{3n-m}{n+1}\) 即可。
于是答案为 \(\binom{3n-m}{n} - \binom{3n-m}{n+1}\)
时间复杂度为 \(O(n)\)

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

using namespace std;
using ll = long long;

const int mod = 998244353;
//const int mod = 1000000007;
struct mint {
    ll x;
    mint(ll x=0):x((x%mod+mod)%mod) {}
    mint operator-() const {
        return mint(-x);
    }
    mint& operator+=(const mint a) {
        if ((x += a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator-=(const mint a) {
        if ((x += mod-a.x) >= mod) x -= mod;
        return *this;
    }
    mint& operator*=(const mint a) {
        (x *= a.x) %= mod;
        return *this;
    }
    mint operator+(const mint a) const {
        return mint(*this) += a;
    }
    mint operator-(const mint a) const {
        return mint(*this) -= a;
    }
    mint operator*(const mint a) const {
        return mint(*this) *= a;
    }
    mint pow(ll t) const {
        if (!t) return 1;
        mint a = pow(t>>1);
        a *= a;
        if (t&1) a *= *this;
        return a;
    }

    // for prime mod
    mint inv() const {
        return pow(mod-2);
    }
    mint& operator/=(const mint a) {
        return *this *= a.inv();
    }
    mint operator/(const mint a) const {
        return mint(*this) /= a;
    }
};
istream& operator>>(istream& is, mint& a) {
    return is >> a.x;
}
ostream& operator<<(ostream& os, const mint& a) {
    return os << a.x;
}

struct modinv {
  int n; vector<mint> d;
  modinv(): n(2), d({0,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(-d[mod%n]*(mod/n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
  int n; vector<mint> d;
  modfact(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*n), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
  int n; vector<mint> d;
  modfactinv(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*invs(n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
  if (n < k || k < 0) return 0;
  return facts(n)*ifacts(k)*ifacts(n-k);
}

void solve() {
    int n;
    cin >> n;
    
    vector<int> p(n);
    rep(i, n) cin >> p[i];
    
    int m = 0;
    rep(i, n) m = max(m, p[i]);
    
    mint ans = comb(3*n-m, n) - comb(3*n-m, n+1);
    cout << ans << '\n';
}

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