【题解】P5643 [PKUWC2018] 随机游走
P5643 [PKUWC2018] 随机游走
题意
给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去。
有 \(Q\) 次询问,每次询问给定一个集合 \(S\),求如果从 \(x\) 出发一直随机游走,直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步。
特别地,点 \(x\)(即起点)视为一开始就被经过了一次。
答案对 \(998244353\) 取模。
\(n\le 18,q\le 5000\)。
题解
知识点:min-max 容斥,动态规划,高位前缀和,FWT。
设当前给定的集合为 \(S\)。
求将 \(S\) 中每个点都经过一次的期望时间,等价于求 \(S\) 中的各个点被经过的时间中的最大值的期望。
比较套路的设问,直接做比较困难,但如果你做过 P4707 重返现世,那么你应该会立即想到 min-max 容斥去把原问题转化。
这是它的基本式子:
有了这个,问题就转变为了求 \(S\) 的所有子集 \(T\) 中的各个点被经过的时间中的最小值的期望,也就是第一次经过 \(T\) 中的点的期望时间。
考虑树形 dp,设 \(f_i\) 为从 \(i\) 出发第一次走到 \(S\) 中的点的期望时间,最终目的就是求出 \(f_x\),那么不妨令 \(x\) 为根。
边界条件,对于 \(\forall i\in S\),有 \(f_i=0\)。
设 \(du_i\) 为点 \(i\) 的度数,\(fa_i\) 为点 \(i\) 的父亲,\(son_i\) 为 \(i\) 的儿子的集合,那么有以下转移:
发现转移成环了,考虑消元。
如果把 \(f_{fa_i}\) 看成自变量,\(f_i\) 看成因变量,这个转移式就很像一个一次函数,设 \(f_i=a_i\times f_{fa_i}+b_i\),带入上式得到:
最终有:
故 \(\displaystyle a_i=\frac{1}{du_i-\sum_{j\in son_i} a_j}\),\(\displaystyle b_i=\frac{du_i+\sum_{j\in son_i} b_j}{du_i-\sum_{j\in son_i} a_j}\),可以发现和 \(fa_i\) 没有任何关系,所以直接朴素树形 dp 求出即可。
由于根节点 \(x\) 不存在 \(fa_x\),故 \(f_x=b_x\)。
\(n\) 很小,考虑 \(O(2^n)\) 枚举所有可能的点集 \(S\),dp 计算出其对应的 \(f_x\),记为 \(ex_S\)。
再回看 min-max 容斥的式子,可以得出,对于一个点集 \(S\),其对应的答案为:
但是询问次数比较多,如果每次枚举子集复杂度会爆炸,考虑预先处理出答案。
观察到,上式的 \((-1)^{|T|-1} ex_T\) 这一项和 \(S\) 一点关系都没有,所以可以直接预处理,令所有 \(ex_S\leftarrow (-1)^{|S|-1} ex_S\),答案又能变为:
这就是高位前缀和的板子了,在这里代码中使用按位或 FWT 处理,同样可以做到 \(O(n2^n)\)。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()
#define N 22
#define M (262144+10)
#define int long long
#define pcnt(x) __builtin_popcount(x)
const int mod=998244353;
int n,m,rt,len;
vector<int>e[N];
inline void add(int &x,int y){
x=(x+y+mod)%mod;
}
inline void dec(int &x,int y){
x=(x-y+mod)%mod;
}
inline int qpow(int a,int b=mod-2){
int ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
inline void fwt(int *f,int n){
int len=2,nx=1;
while(len<=n){
int i=0;
while(i<n){
rep(j,0,nx-1){
add(f[i+j+nx],f[i+j]);
}
i+=len;
}
len<<=1;
nx<<=1;
}
}
bitset<N>f;
int a[N],b[N],ex[M];
inline void dfs(int k,int fa){
if(f[k]){
a[k]=b[k]=0;
return;
}
int du=0,sa=0,sb=0;
for(int x:e[k]){
du++;
if(x==fa){
continue;
}
dfs(x,k);
add(sa,a[x]);
add(sb,b[x]);
}
a[k]=qpow((du-sa+mod)%mod);
b[k]=(du+sb)%mod*qpow((du-sa+mod)%mod);
}
inline int ng(int x){
return x&1?-1:1;
}
inline void init(){
rep(s,0,len-1){
rep(i,1,n){
f[i]=s>>(i-1)&1;
}
dfs(rt,0);
ex[s]=(b[rt]*ng(pcnt(s)+1)+mod)%mod;
}
fwt(ex,len);
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>rt;
len=1<<n;
rep(i,1,n-1){
int u,v;
cin>>u>>v;
e[u].pb(v);
e[v].pb(u);
}
init();
rep(i,1,m){
int c;
cin>>c;
int s=0;
rep(j,1,c){
int x;
cin>>x;
s|=1<<(x-1);
}
cout<<ex[s]<<"\n";
}
return 0;
}

浙公网安备 33010602011771号