题解: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)\) 也没写出来,直接决定了省选被狠狠翻的结局。提醒大家正赛多读几遍题面,不要题面看错想假了,太影响心态了。

posted @ 2026-06-16 18:29  Jenny_yu  阅读(6)  评论(0)    收藏  举报