【题解】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 容斥去把原问题转化。

这是它的基本式子:

\[\displaystyle E(\max(S))=\sum_{T\subseteq S} (-1)^{|T|-1} E(\min(T)) \]

有了这个,问题就转变为了求 \(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\) 的儿子的集合,那么有以下转移:

\[\displaystyle f_i=\frac{1}{du_i}(f_{fa_i}+\sum_{j\in son_i} f_j)+1 \]

发现转移成环了,考虑消元。

如果把 \(f_{fa_i}\) 看成自变量,\(f_i\) 看成因变量,这个转移式就很像一个一次函数,设 \(f_i=a_i\times f_{fa_i}+b_i\),带入上式得到:

\[\displaystyle f_i=\frac{1}{du_i}(f_{fa_i}+\sum_{j\in son_i} (a_j\times f_i+b_j))+1 \]

\[\displaystyle du_i\times f_i=f_{fa_i}+\sum_{j\in son_i} (a_j\times f_i+b_j)+du_i \]

\[\displaystyle (du_i-\sum_{j\in son_i} a_j)\times f_i=f_{fa_i}+\sum_{j\in son_i} b_j+du_i \]

最终有:

\[\displaystyle f_i=\frac{1}{du_i-\sum_{j\in son_i} a_j}\times f_{fa_i}+\frac{du_i+\sum_{j\in son_i} b_j}{du_i-\sum_{j\in son_i} a_j} \]

\(\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\),其对应的答案为:

\[\displaystyle \sum_{T\subseteq S} (-1)^{|T|-1} ex_T \]

但是询问次数比较多,如果每次枚举子集复杂度会爆炸,考虑预先处理出答案。

观察到,上式的 \((-1)^{|T|-1} ex_T\) 这一项和 \(S\) 一点关系都没有,所以可以直接预处理,令所有 \(ex_S\leftarrow (-1)^{|S|-1} ex_S\),答案又能变为:

\[\displaystyle \sum_{T\subseteq S} ex_T \]

这就是高位前缀和的板子了,在这里代码中使用按位或 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;
}
posted @ 2025-08-14 22:37  Lucyna_Kushinada  阅读(17)  评论(0)    收藏  举报