题解:[PKUWC2018] 随机游走

题目传送门

题意分析

Min-max 容斥

记走到点 \(i\) 的期望步数为 \(t_i\)\(S=\set{1,2,3,\cdots,n}\),题目所求即:

\[E\left(\max_{i\in S}t_i\right) \]

根据 Min-max 容斥,等价于求:

\[\sum_{T\subseteq S}(-1)^{\vert T\vert-1}E\left(\min_{i\in T}t_i\right) \]

树型 DP

注意到 \(n\leq18\),因此枚举 \(T\) 是可以被接受的,考虑如何对确定的 \(T\) 计算 \(E\left(\min\limits_{i\in T}t_i\right)\)

这其实就是从点 \(x\) 开始,第一次走到 \(T\) 中的任意一点的期望步数

设从点 \(i\) 开始,第一次走到 \(T\) 中任意一点的期望步数为 \(f_i\)。若 \(i\in T\),则 \(f_i=0\)

将原图视为以 \(x\) 为根节点的树,记 \(\textit{son}_i\) 表示节点 \(i\) 的子节点集,\(\textit{deg}_i\) 表示节点 \(i\) 的度数,\(\textit{father}_i\) 表示节点 \(i\) 的父节点。

则有:

\[f_u=\dfrac{1}{\textit{deg}_u}\left(f_{\textit{father}_u}+\sum_{v\in\textit{son}_u}f_v\right)+1 \]

但是,这并不好操作。因为可以用 \(n\) 个点列出 \(n\) 个方程后高斯消元,但是这样总时间复杂度就是 \(\mathcal O(n^32^n)\),无法接受。

考虑利用树的性质(半线性结构,一个节点具有唯一的父节点)。

\(f_u=k_u\cdot f_{\textit{father}_u}+b_u\),则有:

\[\begin{aligned} f_u&=\frac1{\textit{deg}_u}\left(f_{\textit{father}_u}+\sum_{v\in\textit{son}_u}(k_v\cdot f_u+b_v)\right)+1\\ \textit{deg}_uf_u&=f_{\textit{father}_u}+\sum_{v\in\textit{son}_u}(k_v\cdot f_u+b_v)+\textit{deg}_u\\ \textit{deg}_uf_u&=f_{\textit{father}_u}+\sum_{v\in\textit{son}_u}k_v\cdot f_u+\sum_{v\in\textit{son}_u}b_v+\textit{deg}_u\\ \left(\textit{deg}_u-\sum_{v\in\textit{son}_u}k_v\right)f_u&=f_{\textit{father}_u}+\sum_{v\in\textit{son}_u}b_v+\textit{deg}_u\\ f_u&=\dfrac{1}{\textit{deg}_u-\sum\limits_{v\in\textit{son}_u}k_v}f_{\textit{father}_u}+\dfrac{\sum\limits_{v\in\textit{son}_u}b_v+\textit{deg}_u}{\textit{deg}_u-\sum\limits_{v\in\textit{son}_u}k_v} \end{aligned} \]

考虑到上面设的参数 \(f_u=k_u\cdot f_{\textit{father}_u}+b_u\),则有:

\[\begin{cases} k_u&=\dfrac{1}{\textit{deg}_u-\sum\limits_{v\in\textit{son}_u}k_v}\\ b_u&=\dfrac{\sum\limits_{v\in\textit{son}_u}b_v+\textit{deg}_u}{\textit{deg}_u-\sum\limits_{v\in\textit{son}_u}k_v} \end{cases} \]

因此,可以通过树型 DP 求出每一个点的 \(k_i,v_i\)。考虑到根节点 \(x\) 没有父节点,在初始公式 \(f_u=\dfrac{1}{\textit{deg}_u}\left(f_{\textit{father}_u}+\sum\limits_{v\in\textit{son}_u}f_v\right)+1\)\(f_{\textit{father}_u}\) 就不应当出现,因此可视为 \(0\),故:

\[f_x=b_x \]

高维前缀和

枚举一次 \(S\) 的子集是 \(\mathcal O\left(2^n\right)\) 的,在 \(Q\leq5000\) 次询问中重复做显然是不合理的,因为 Min-max 容斥中的 \(E\left(\max\limits_{i\in S}t_i\right)\)\(S\) 无关。

因此可以考虑预处理出所有答案,直接用 \(S\) 查询,那么便可以使用高维前缀和

\(\textit{ans}_T\) 表示 \(T\) 的答案,那么记 \(\textit{ans}'_T\) 表示 \(T\)\(T\)所有子集的答案之和。

可以枚举 \(i\in[1,n]\),若 \(T\) 包含 \(i\),那么就让 \(\textit{ans}'_T\) 加上 \(\textit{ans}'_{T\setminus\lbrace i\rbrace}\) 即可。其中,\(T\setminus\set{i}\) 表示 \(T,\set{i}\) 的差集。

参考代码

for(int i=0;i<n;i++){
    for(int T=0;T<=all;T++){
        if(T&(1<<i)){
            ans[T]=(1ll*ans[T]+ans[T^(1<<i)])%P;
        }
    }
}

AC 代码

//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=18,P=998244353;
int qpow(int base,int n){
	base%=P;
	int ans=1;
	while(n){
		if(n&1){
			ans=1ll*ans*base%P;
		}
		base=1ll*base*base%P;
		n>>=1; 
	}
	return ans;
}
int n,x;
int ans[1<<N|1];
vector<int>g[N+1];
int k[N+1],b[N+1];
int &root=x;
void dfs(int x,int fx,int T){
	int ans=0;
	int sumK=0,sumB=0;
	for(int i:g[x]){
		if(i==fx){
			continue;
		}
		dfs(i,x,T);
		sumK=(1ll*sumK+k[i])%P;
		sumB=(1ll*sumB+b[i])%P;
	}
	if(T&(1<<x-1)){
		k[x]=b[x]=0;
	}else{
        //一个坑:size()是无符号整数,注意类型转换。
		k[x]=qpow((ll)g[x].size()-sumK,P-2);
		b[x]=((ll)g[x].size()+sumB)%P*k[x]%P;
	}
}
//统计集合大小
int popcount(int n){
	int ans=0;
	while(n){
		ans++;
		n-=n&-n;
	}
	return ans;
} 
void pre(){
	int all=(1<<n)-1;
	for(int T=0;T<=all;T++){
		dfs(x,0,T);
		ans[T]=b[x]; 
		if((popcount(T)+1)&1){
			ans[T]=-ans[T];
		}
	}
	for(int i=0;i<n;i++){
		for(int T=0;T<=all;T++){
			if(T&(1<<i)){
				ans[T]=(1ll*ans[T]+ans[T^(1<<i)])%P;
			}
		}
	}
}
int query(int S){
	if(ans[S]<0){
		ans[S]+=P;
	}
	return ans[S];
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int q;
	cin>>n>>q>>x;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	pre(); 
	while(q--){
		int pl;
		cin>>pl;
		int S=0;
		while(pl--){
			int p;
			cin>>p;
			S|=1<<p-1;
		}
		cout<<query(S)<<'\n';
	}
	
	cout.flush(); 
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2025-07-22 19:25  TH911  阅读(6)  评论(0)    收藏  举报