题解:[PKUWC2018] 随机游走
题意分析
Min-max 容斥
记走到点 \(i\) 的期望步数为 \(t_i\),\(S=\set{1,2,3,\cdots,n}\),题目所求即:
根据 Min-max 容斥,等价于求:
树型 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\) 的父节点。
则有:
但是,这并不好操作。因为可以用 \(n\) 个点列出 \(n\) 个方程后高斯消元,但是这样总时间复杂度就是 \(\mathcal O(n^32^n)\),无法接受。
考虑利用树的性质(半线性结构,一个节点具有唯一的父节点)。
设 \(f_u=k_u\cdot f_{\textit{father}_u}+b_u\),则有:
考虑到上面设的参数 \(f_u=k_u\cdot f_{\textit{father}_u}+b_u\),则有:
因此,可以通过树型 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\),故:
高维前缀和
枚举一次 \(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;
}