[PKUWC2018] 随机游走
水黑。
题目链接: [PKUWC2018] 随机游走 。
题目内容:
给定一棵 n 个结点的树,你从点 x 出发,每次等概率随机选择一条与所在点相邻的边走过去。
有 Q 次询问,每次询问给定一个集合 S,求如果从 x 出发一直随机游走,直到点集 S 中所有点都至少经过一次的话,期望游走几步。
特别地,点 x(即起点)视为一开始就被经过了一次。
答案对 998244353 取模。
n≤18,Q≤5×103 。
首先将 x 固定为根。
设 ti 表示节点 i 第一次被访问到的期望时间,则询问的答案为 E[i∈Smaxti] 。
使用 min-max 容斥,则:
E[maxS]=T⊆S∑(−1)∣T∣+1E[minT]
由于数据范围较小,我们考虑直接预处理出所有的 E[minT] ,使用树形 DP 可以很方便的 O(n) 求解。
最后一个 FWT 结束。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <bits/stdc++.h> using namespace std; const int N=19,M=1<<18,P=998244353; int n,Q,rt,k[N],b[N],f[M],d[N],G[N<<1],u[N],v[N],deg[N]; int kpow(int a,int b){ int t=1; while(b){ if(b&1)t=1ll*t*a%P; a=1ll*a*a%P; b>>=1; } return t; } void dfs(int x,int fa,int S){ int sum=deg[x],up=deg[x]; for(int i=d[x];i<d[x+1];i++){ int u=G[i]; if(u==fa)continue; dfs(u,x,S); (sum+=P-k[u])%=P; (up+=b[u])%=P; } k[x]=kpow(sum,P-2); b[x]=1ll*up*k[x]%P; if((S>>x-1)&1)k[x]=b[x]=0; } void init(int S){ memset(k,0,sizeof(k)); memset(b,0,sizeof(b)); dfs(rt,0,S); f[S]=(__builtin_popcount(S)&1?b[rt]:(P-b[rt])%P); } void FWT(int *f){ for(int i=2;i<=(1<<n);i<<=1) for(int j=0;j<(1<<n);j+=i) for(int k=j;k<j+(i>>1);k++) (f[k+(i>>1)]+=f[k])%=P; } int main(){ scanf("%d%d%d",&n,&Q,&rt); for(int i=1;i<n;i++)scanf("%d%d",u+i,v+i),d[u[i]]++,d[v[i]]++; for(int i=1;i<=n;i++)deg[i]=d[i]; for(int i=1;i<=n;i++)d[i+1]+=d[i]; for(int i=1;i<n;i++){ G[--d[u[i]]]=v[i]; G[--d[v[i]]]=u[i]; } for(int i=1;i<(1<<n);i++){ init(i); } FWT(f); while(Q--){ int K,S=0; scanf("%d",&K); for(int i=1,x;i<=K;i++)scanf("%d",&x),S|=(1<<x-1); printf("%d\n",f[S]); } return 0; }
|