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:
暴力的式子:
注意到这个式子与 \(s_v\) 有关,于是我们设其为 \(f(S)\),但这样枚举好像没有什么优化的出路。
为了让我们快速出 \(f(s)\),我们希望里面的组合数与 \(S\) 无关,于是我们考虑其组合意义:
从上式本质插板法入手,这里有 \(m\) 个球,其中我们枚举前 \(i\) 组刚好填了 \(>m/2\) 个球,则:
预处理出 \(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;
}

浙公网安备 33010602011771号