题解:P15649 [省选联考 2026] 找寻者 / recollector
P15649 [省选联考 2026] 找寻者 / recollector
被D1T1严肃创飞。
40pts,\(O(n^3)\)
首先是题意转换:要求从结点 x 到根结点 1 的简单路径所包含的轻边数量的期望之和,等价于求(每条边成为轻边的概率)乘(这条边靠近叶子的点的子树大小)。因为子树里的每个点都会经过一次这条边。
注意重链长度的定义为链上节点的数量而非边的数量。
需要维护 \(f_{x,i}\) 表示 \(x\) 节点所在的重链长度。设 \(y\) 是 \(x\) 的儿子,考虑从 \(f_{y,*}\) 转移到 \(f_{x,*}\),发现需要另外维护一个背包 \(h_i\) 表示背包中点所在重链长度和为 \(i\) 的概率。对所有 \(x\) 的儿子(除了 \(y\))加入背包。
背包加入点 \(z\):
\[h_i=\sum_{j=1}^{min(i,sz_z)}h_{i-j} \times f_{z,j}
\]
转移:
\[f_{x,i+1} = f_{y,i} \times \frac{i}{i+j} \times h_j
\]
100pts,\(O(n^2)\)
考虑优化求背包。使用可撤背包,先把所有儿子扔进背包,然后转移 \(f\) 的时候退掉相应儿子。
观察 \(h_i\) 的转移式。设 \(h'\) 为转移后的新背包:
\[h'_i=h_{i-a} \times f_{z,a}+\sum_{j=a+1}^{min(i,sz_z)}h_{i-j} \times f_{z,j}
\]
其中 \(a\) 表示满足 \(f_{z,j} \not= 0\) 的最小非零正数。
\[h_{i-a}=(h'_i-\sum_{j=a+1}^{min(i,sz_z)}h_{i-j} \times f_{z,j}) \times f_{z,a}^{-1}
\]
\(h_{i-j}\) 一定比 \(h_{i-a}\) 要小,\(h'_i\) 是退背包前的数据,因此从小到大撤掉即可。注意如果 \(i-a\) 没越界, \(i\) 越界了,要特殊判 \(0\)。
这题需要卡常。实现细节方面,注意背包合并只合并有效部分,提前算出 \(a\) 可以在合并儿子全集背包的时候跳过 \(f_{a,*}\) 的前缀 0。逆元提前算好否则复杂度多挂一个 \(logP\)。非 dfs 函数加 inline 快了好多。
点击查看代码
#include <bits/stdc++.h>
typedef unsigned long long ll;
using namespace std;
const int N = 5010;
const int p = 998244353;
int n,hed[N],to[N * 2],nxt[N * 2],tote,sz[N],deg[N],low[N];
ll f[N][N],h[N],g[N],inv[N],t[N],ans;
ll rd(){
ll s = 0;char c = getchar();ll f = 1;
for(; c < '0' || c > '9'; c = getchar())((c == '-') && (f = -1));
for(; c >= '0' && c <= '9'; c = getchar())s = (s << 1) + (s << 3) + (c ^ 48);
return s * f;
}
void init(){
memset(hed,0,sizeof(hed));memset(to,0,sizeof(to));memset(nxt,0,sizeof(nxt));
memset(f,0,sizeof(f));memset(h,0,sizeof(h));memset(sz,0,sizeof(sz));memset(deg,0,sizeof(deg));
tote = ans = 0;
}
inline void addedge(int x,int y){
tote++,nxt[tote] = hed[x],to[tote] = y,hed[x] = tote,deg[x]++;
}
inline ll poww(ll x,int y){
ll res = 1;
while(y){
if(y & 1)res = res * x % p;
x = x * x % p;
y >>= 1;
}
return res;
}
inline void add(ll &x,ll y){x += y;if(x >= p){x -= p;}}
inline void del(ll &x,ll y){(x < y ? (x = x + p - y) : (x -= y));}
void dfs(int x,int fa){
sz[x] = 1;
for(int i = hed[x]; i; i = nxt[i]){
int y = to[i];
if(y == fa)continue;
dfs(y,x),sz[x] += sz[y];
}
if(deg[x] == 1){f[x][1] = 1;low[x] = 1;return;}
for(int i = 0; i <= sz[x]; i++)h[i] = 0;
sz[x] = 1;h[0] = 1;
int lm;
for(int i = hed[x]; i; i = nxt[i]){
int y = to[i];
if(y == fa)continue;
sz[x] += sz[y];
for(int k = sz[x]; k >= 0; k--){
h[k] = 0;lm = min(sz[y],k);
for(int l = max(low[y],k-(sz[x] - sz[y])); l <= lm; l++)add(h[k],h[k - l] * f[y][l] % p);
}
}
for(int i = hed[x]; i; i = nxt[i]){
int y = to[i];
if(y == fa)continue;
//cout << y << " " << low[y] << endl;
ll iv = poww(f[y][low[y]],p - 2);
for(int k = 0; k <= sz[x] - sz[y]; k++){
if(k + low[y] > sz[x]){t[k] = 0;continue;}
t[k] = h[k + low[y]];lm = min(sz[y] - low[y],k);
for(int l = 1; l <= lm; l++)del(t[k],t[k - l] * f[y][l + low[y]] % p);
t[k] = t[k] * iv % p;
}
g[y] = 1;
for(int j = 1; j <= sz[y]; j++){
for(int k = 0; k <= sz[x] - sz[y]; k++){
add(f[x][j + 1],f[y][j] * j % p * inv[j + k] % p * t[k] % p);
del(g[y],f[y][j] * j % p * inv[j + k] % p * t[k] % p);
}
}
add(ans,g[y] * sz[y] % p);
}
low[x] = 1;
while(!f[x][low[x]] && low[x] + 1 <= sz[x])low[x]++;
}
void solve(){
n = rd();
for(int i = 1,x,y; i < n; i++)x = rd(),y = rd(),addedge(x,y),addedge(y,x);
dfs(1,0);printf("%llu\n",ans);
}
signed main(){
for(int i = 1; i <= 5005; i++)inv[i] = poww(i,p - 2);
int c = rd(),t = rd();
while(t--){
init();solve();
}
return 0;
}
最后
场上没脑子,\(O(n^3)\) 也没写出来,直接决定了省选被狠狠翻的结局。提醒大家正赛多读几遍题面,不要题面看错想假了,太影响心态了。

浙公网安备 33010602011771号