比赛总结:MX-J10&X8

赛时得分:\((100 + 100 + 100 + 100 + 18 + 0 + 0 + 0) / 800\)

题目链接:MX-J10 MX-X8

T0

按照题意模拟即可。

T1

Tag:贪心

\(b_i=x-a_i\),求 \(b\) 的最大字段和 \(x\),答案为 \((\sum a) + x\)

T2

Tag:构造

任何操作都可以等价拆解成形如 \((1,1)\) 的操作,那么对于每个 \(1\le i<n\),操作 \(a_i\)\(a_{i+1}\),直到 \(a_i=b_i\)。最后判断 \(a_n\) 是否等于 \(b_n\) 即可。

T3

Tag:构造,DSU

假设当前快乐值为 \(a\),走过的路径长度为 \(d\)。之后再走了 \(b\) 长度的路径,能量值为 \(b\),那么若 \(a\)\(d\) 奇偶性相同,显然 \((d+b)\)\((a\oplus b)\) 的奇偶性相同。所以根据数学归纳法(边界从 \(a\)\(d\) 都是 \(0\) 开始),我们最终得到的 \(a\) 和路径长度的奇偶性相同,那么只需要判断 \(s\)\(t\) 是否存在一条长度的奇偶性与 \(x\) 相同的路径即可。

若存在,我们可以先走到 \(t\),假设当前快乐值是 \(a\),然后沿着任意一条边来回走 \((x\oplus a)\) 次,即可。若不存在,根据上面的结论,显然构造不出答案。当然需要特判 \(s=t\) 的情况。

如何判断是否存在这样的路径?首先用一个并查集判掉 \(s\)\(t\) 不连通的情况。然后用另一个并查集,将每个点拆成 \(odd_i\)\(even_i\),对于原图的每条边 \((u,v)\),将 \(odd_u\)\(even_v\) 连通,将 \(even_u\)\(odd_v\) 连通,即可。

Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 2e5 + 5;

int n, m, q;

struct DSU {
    int n;
    vector<int> f, siz;
    void init(int _n) {
        n = _n;
        f.assign(n + 5, 0);
        siz.assign(n + 5, 1);
        for (int i = 1; i <= n; i++) f[i] = i;
    }
    int find(int x) {
        return (f[x] == x ? x : f[x] = find(f[x])); // 路径压缩
    }
    void merge(int x, int y) {
        int fx = find(x), fy = find(y);
        if (fx == fy) return;
        siz[fy] += siz[fx];
        f[fx] = fy;
    }
    bool is_merge(int x, int y) {
        return (find(x) == find(y));
    }
} dsu, dsu2;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m >> q;
    dsu.init(n);
    dsu2.init(n * 2); // u:odd, n+u:even
    int u, v;
    for (int i = 1; i <= m; i++) {
        cin >> u >> v;
        dsu.merge(u, v);
        dsu2.merge(n + u, v);
        dsu2.merge(u, n + v);
    }
    int s, t, x;
    while (q--) {
        cin >> s >> t >> x;
        if (!dsu.is_merge(s, t)) {
            cout << "expand\n";
        } else if (x & 1) {
            if (dsu2.is_merge(s, n + t)) {
                cout << "tribool\n";
            } else {
                cout << "expand\n";
            }
        } else {
            if (s == t) {
                if (x == 0) {
                    cout << "tribool\n";
                } else if (dsu.siz[dsu.find(s)] > 1) {
                    cout << "tribool\n";
                } else {
                    cout << "expand\n";
                }
            } else if (dsu2.is_merge(s, t)) {
                cout << "tribool\n";
            } else {
                cout << "expand\n";
            }
        }
    }
    return 0;
}

T4

Tag:计数,构造

首先考虑一个问题:给定一个有根树,对于每个点 \(u\),给定儿子个数 \(a_u\) 以及每个儿子的子树大小 \(b_{u,j}\),求方案数。

\(cnt_{u,j}\) 表示 \(b_{u,i}=j\)\(i\) 的个数。记 \(siz_u\) 表示 \(u\) 的子树大小,记 \(tot_{j}\) 表示 \(siz_i = j\)\(i\) 的个数。

那么答案为 \(\prod\limits_{i=1}^{n} \prod\limits_j \binom{tot_j - \sum\limits_{k=1}^{i-1}cnt_{k,j}}{cnt_{i,j}}\)

对于这道题目,我们可以让一个重心为根,因为重心连向的每个连通块大小不超过 \(n/2\),所以可以确定重心。然后对于其他点,删去 \((\ge n/2)\) 的那个连通块的信息,就可以转化为上面的问题。

Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 3e5 + 5, MOD = 998244353, INF = 1e9;

int n, a[N], root, siz[N], cnt[N];
int fact[N], inv[N];
vector<int> b[N];

int fpow(int a, int b) {
    int ans = 1;
    for (; b; b >>= 1, a = 1ll * a * a % MOD) {
        if (b & 1) ans = 1ll * ans * a % MOD;
    }
    return ans;
}

int C(int n, int m) {
    if (n < m) return 0;
    return 1ll * fact[n] * inv[m] % MOD * inv[n - m] % MOD;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n;

    fact[0] = 1;
    for (int i = 1; i <= n; i++) fact[i] = 1ll * fact[i - 1] * i % MOD;
    inv[n] = fpow(fact[n], MOD - 2);
    for (int i = n - 1; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        int x;
        for (int j = 1; j <= a[i]; j++) {
            cin >> x;
            b[i].push_back(x);
        }
        sort(b[i].begin(), b[i].end());
        if (b[i][a[i] - 1] <= n / 2) root = i;
    }

    for (int i = 1; i <= n; i++) {
        if (i != root && b[i][a[i] - 1] >= n / 2) {
            b[i].pop_back();
            a[i]--;
        }
        for (int j : b[i]) {
            cnt[j]++;
        }
    }

    int ans = 1;
    for (int i = 1; i <= n; i++) {
        int x = 0;
        for (int j = 0; j < a[i]; j++) {
            x++;
            if (j == a[i] - 1 || b[i][j] != b[i][j + 1]) {
                ans = 1ll * ans * C(cnt[b[i][j]], x) % MOD;
                cnt[b[i][j]] -= x;
                x = 0;
            }
        }
    }

    cout << ans << '\n';

    return 0;
}
posted @ 2025-01-31 23:31  chenwenmo  阅读(13)  评论(0)    收藏  举报