Loading

8.4 NOIP 模拟赛

T1

给定一张无向图,从 \(1\)\(n\) 依次删点,并将其邻点连成一个团。问最后会多加多少条边。\(O(n\log n)\)

考场思路:刻画怎样的两点 \((u,v)\) 会联通,不难发现当且仅当存在路径 \(u\to a_1\to a_2\to\cdots\to a_m\to v\)\(a_1<a_2<\cdots<a_m<x,y\) 。然而这还是不好做,但这或许可以启发我们将团信息仿照单调链向外推。于是后来想到直接用 set 维护模拟的过程,然而没有钦定统计顺序导致混乱。

sol:

边很多,先确定连边顺序。为了防止从维护的集合中扣去已删点,我们钦定从小到大连边。

给每个点用 set 维护其当前邻点(从小往大)。在删点时只需要将当前点的集合并到最小邻点的集合上去,使用启发式合并做到 \(O(n\log n)\)

T2

\(3\) 座魔塔 \(A,B,C\) ,每座塔都有 \(n\) 个关卡,有 \(n\) 种钥匙,给定每座塔 \(n\) 个关卡需要的钥匙(是一个排列) \({P_{A/B/C}}_i\)。给定每个关卡的收益 \({A_{A/B/C}}_i\),给定目前每种钥匙数 \(k_i\in{1,2}\),求最大收益。
\(n\le 10^5\)

考场思路:其实没怎么想这道题...因为时间全耗在 T1 上了... 瞄了一眼直接觉得是贪心,一看还有决策顺序要求,直接 Monster Hunter Trick 板上钉钉(?)

sol:

先考虑暴力怎么做:枚举 \(A,B\) 走到哪了,此时 \(C\) 是唯一确定的。\(O(n^2)\)

考虑能不能只扫描 \(A\),实时维护收益关于 \(B\) 的函数? 发现添加 \(A\) 的一个关卡,会导致 \(B\)\(C\) 卡在某一关(\(k=1\)),或者 \(B\) 在某处用掉一个钥匙后 \(C\) 会卡关(\(k=2\))。

所以可以直接线段树维护,操作形如在一段后缀对 \(sum_c{j}\)\(\min\)

T3

给定一棵树,求每一种满足 \(\sum{a_i}=m,a_i\ge 0\) 的权值分配方案的带权重心编号和,对 \(998244353\) 取模。
\(n\le 2\times 10^5,m\le 5\times 10^6\)

考场思路:当时只会 \(m\) 为奇数的 \(O(nm)\),没啥思维难度,就是枚举重心然后用总方案减去有一个子树大于 \(m\over 2\) 的方案

sol:

暴力的式子:

\[\sum_{i=m/2+1}^{m}\binom{i+s_v-1}{i}\binom{m-i+s_v-1}{m-i} \]

注意到这个式子与 \(s_v\) 有关,于是我们设其为 \(f(S)\),但这样枚举好像没有什么优化的出路。

为了让我们快速出 \(f(s)\),我们希望里面的组合数与 \(S\) 无关,于是我们考虑其组合意义:

从上式本质插板法入手,这里有 \(m\) 个球,其中我们枚举前 \(i\) 组刚好填了 \(>m/2\) 个球,则:

\[f(S)=\sum_{i=1}^S\binom{i+\left\lfloor\frac m2\right\rfloor-1}{\left\lfloor\frac m2\right\rfloor}\binom{n+\left\lceil\frac m2\right\rceil-1-i}{\left\lceil\frac m2\right\rceil-1} \]

预处理出 \(f\)

于是 \(m\) 为奇数就做完了,只有一个重心。

\(m\) 为偶数的情况,有一个经典结论,就是此时带权重心成一条链,且链两端子树大小为 \(m/over 2\),链中间的点及其子树全部填 \(0\)

一个比较直接的想法是我们对这条链点分治。接下来我们讨论的子树都是在原树上以当前分治重心为根的子树。

于是我们维护每个点到当前分治重心的最小值,和这个点作为链端点的方案数。注意:为了保证其真的是端点,应至少有两棵子树不为空

合并两条到分治重心的直链是容易的,使用树状数组。注意单独考虑到分治重心直链。

最后还要计算单点的贡献。与奇数做法的差别在于,应当去掉其作为带权重心链上点的贡献。于是应当减去:有两个子树大小为 \(m\over 2\) 的子节点,有一个子节点子树大小为 \(m\over 2\) 且其他值分布在另外至少两个子节点子树内。

咕咕咕。。。

代码放这里,明天调:

show code
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define int long long
#define RD read()
#define PN putchar('\n')
using namespace std;
bool MemoryBegin;
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
    while('0' <= ch && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    return x * f;
}

int const N = 2e5 + 5, M = 5e6 + 2e5 + 5, P = 998244353;

int n, m;
vector<int> G[N];

int inv[M], fac[M], invfac[M];
int C(int n, int m) {
    if(n < 0 || m < 0 || n < m) return 0;
    return fac[n] * invfac[m] % P * invfac[n - m] % P;
}
void init(int lim) {
    inv[1] = fac[1] = fac[0] = invfac[1] = invfac[0] = 1;
    rep(i, 2, lim) {
        inv[i] = (P - P / i * inv[P % i] % P) % P;
        fac[i] = fac[i - 1] * i % P;
        invfac[i] = invfac[i - 1] * inv[i] % P;
    }
}
void inc(int &a, int b) { a += b; a = (a >= P ? a - P : a); }

#define calc(m, n) C(n + m - 1, n - 1)
int f[N], siz[N], F[N], dfn[N], idx;
void dfs0(int u, int pre) {
    siz[u] = 1, dfn[u] = ++ idx;
    f[u] = calc(m, n);
    for(int v : G[u]) if(v != pre) {
        dfs0(v, u);
        siz[u] += siz[v];
        inc(f[u], P - F[siz[v]]);
    }
    inc(f[u], P - F[n - siz[u]]);
}
int size(int x, int fa) {
    if(fa == x) return n;
    return dfn[fa] <= dfn[x] && dfn[x] <= dfn[fa] + siz[fa] - 1 ? siz[x] : n - siz[fa];
}
int ans;

namespace point_divide_conquer {
    int root, mns, all, siz[N];
    bool del[N];
    vector<pair<int, int> > vec;
    void find_root(int u, int pre, int mn = -1) {
        int s = 0;
        siz[u] = 1;
        for(int v : G[u]) if(v != pre) {
            find_root(v, u, min(mn, u));
            s = max(s, siz[v]);
            siz[u] += siz[v];
        } s = max(s, n - siz[u]);
        if(s < mns) mns = s, root = u;
        if(mn != -1) {
            int val = calc(m / 2, size(u, pre));
            // printf("u = %lld, val = %lld, size = %lld\n", u, val, size(u, pre));
            for(int v : G[u]) if(v != pre) {
                inc(val, P - calc(m / 2, size(v, u)));
            }
            vec.push_back({mn, val});
        }
    }

    struct BIT {
        int t[N];
        void add(int x, int v) { x ++; while(x <= n + 1) inc(t[x], v), x += x & -x; }
        int sum(int x) { x ++; int res = 0; while(x) inc(res, t[x]), x -= x & -x; return res; }
    } bit1, bit2;

    void solve(int u) {
        del[u] = 1;
        find_root(u, 0);
        int sub = 0, sum = 0;
        for(int v : G[u]) inc(sub, P - calc(m / 2, size(v, u)));
        vector<int> nxt;
        vector<pair<int, int> > clearq;
        // printf("now center is #%lld\n", u);
        for(int v : G[u]) {
            int coef = calc(m / 2, size(v, u)), out = coef;
            inc(out, sub); inc(out, calc(m / 2, n - size(v, u)));
            inc(f[u], P - out * coef % P);
            inc(f[u], P - sum * coef % P);
            inc(sum, coef);
            if(!del[v]) {
                vec.clear();
                mns = all = siz[v], root = v;
                find_root(v, u, min(v, u));
                nxt.push_back(root);
                for(auto ele : vec) {
                    int mn = ele.first, val = ele.second;
                    // printf(" (%lld, %lld)\n", mn, val);
                    inc(ans, mn * val % P * bit1.sum(n - mn + 1) % P);
                    inc(ans, val * bit2.sum(mn - 1) % P);
                    inc(ans, mn * val % P);
                }
                for(auto ele : vec) {
                    int mn = ele.first, val = ele.second;
                    bit1.add(n - mn + 1, val);
                    bit2.add(mn, val * mn % P);
                    clearq.push_back(ele);
                }
            }
        }
        for(auto ele : clearq) {
            bit1.add(n - ele.first + 1, P - ele.second);
            bit2.add(ele.first, P - ele.first * ele.second % P);
        }
        for(int x : nxt) solve(x);
    } 
    void work() {
        all = mns = n, root = 1;
        find_root(1, 0);
        solve(root);
    }
}
using point_divide_conquer :: work;

signed main() {
    freopen("star.in", "r", stdin);
    freopen("star.out", "w", stdout);
    //cerr << fabs(&MemoryBegin - &MemoryEnd) / 1048576.0 << " MB\n";
    n = RD, m = RD;
    init(n + m);
    rep(i, 1, n - 1) {
        int u = RD, v = RD;
        G[u].push_back(v), G[v].push_back(u);
    }
    rep(i, 1, n - 1) F[i] = C(i + m / 2 - 1, m / 2) * C((m - 1) / 2 + n - i, (m - 1) / 2) % P;
    rep(i, 1, n - 1) inc(F[i], F[i - 1]);
    rep(i, 1, n - 1) printf("F[%lld] = %lld\n", i, F[i]);
    dfs0(1, 0);
    rep(i, 1, n) printf("f[%lld] = %lld\n", i, f[i]);
    if(!(m & 1)) work();
    rep(i, 1, n) inc(ans, i * f[i] % P);
    printf("%lld\n", ans);
    // cerr << "\n" << clock() * 1.0 / CLOCKS_PER_SEC * 1000 << " ms\n";
    return 0;
}
posted @ 2025-08-06 19:29  慕斯ひいきする  阅读(12)  评论(0)    收藏  举报