LOJ #2542「PKUWC2018」隨機游走


$ Min$-$Max$容斥真好用

$ PKUWC$滾粗后這題一直在$ todolist$里

今天才補掉..還要更加努力啊..

LOJ #2542

題意:給一棵不超過$ 18$個節點的樹,$ 5000$次詢問,每次問從根隨機游走走遍一個集合的期望步數


$ Solution:$

考慮$ Min$-$Max$容斥

有$ Max(S)=\sum\limits_{T \subseteq S}(-1)^{|T|+1}Min(T)$

其中$ S,T$是一個集合,$Max(S)$表示$ S$中最大元素,$Min(S)$同理

$ update$評論區已經給出證明

 

我們設集合$ S$表示走到每個點的期望時間

顯然走遍一個集合的期望時間就是$ Max(S)$

且第一次走到某集合的期望時間是$ Min(S)$

$ Max(S)$不容易計算,我們轉而求解$ Min(S)$

令$f_i$表示從點$ i$隨機游走第一次走到集合$ S$的期望步數

這個顯然可以高斯消元,不過復雜度略大

由於轉移是在樹上,可以直接在樹上$ O(n)$消元

我們令$ f_i=k_if_{fa[i]}+b_i$

當$ i$在集合$ S$中的時候$k_i=b_i=0$否則進行轉移

轉移的時候把當前點孩子的$ k$和$ b$算出然后代到當前點的方程中算出當前點的$ k$和$ b$

這樣可以在$O(2^n·n*算逆元復雜度)$的時間復雜度內算出所有的$ Min(S)$

 

然后直接容斥算$ Max(S)$,復雜度是$ 5000*2^n$的

我們可以提前預處理每個$ Max(S)$每次枚舉子集轉移,時間復雜度是$ 3^n$的

據說都能過

不過其實這部分可以優化到$ 2^n*n$的

我們直接根據$ popcount(S)$的奇偶性來判斷是否給$ Min(S)$乘上$ -1$

然后直接高維前綴和即可


$ my \ code:$

#include<ctime>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define p 998244353
#define rt register int
#define ll long long
using namespace std;
inline ll read(){
    ll x = 0; char zf = 1; char ch = getchar();
    while (ch != '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
void writeln(const ll y){write(y);putchar('\n');}
int i,j,k,m,n,x,y,z,cnt,Root;
struct ret{
    int k,b;//k*father + b
    ret operator +(const ret s)const{
        return {(k+s.k)%p,(b+s.b)%p};
    }
    ret operator *(const int s)const{
        return {1ll*k*s%p,1ll*b*s%p};
    }
}f[19][1<<18];
int F[45],L[45],N[45],a[45],d[45],inv[45],Min[1<<18];
void add(int x,int y){
    a[++k]=y;
    if(!F[x])F[x]=k;
    else N[L[x]]=k;
    L[x]=k; 
}
int ksm(int x,int y){
    int ans=1;
    for(rt i=y;i;i>>=1,x=1ll*x*x%p)if(i&1)ans=1ll*x*ans%p;;
    return (ans+p)%p;
}
ret dfs(int x,int pre,int s){
    if(s>>x-1&1)return{0,0}; 
    ret now={0,0};
    for(rt i=F[x];i;i=N[i])if(a[i]!=pre)now=now+dfs(a[i],x,s)*inv[d[x]];
    const int Inv=ksm(1-now.k,p-2);
    return {1ll*Inv*inv[d[x]]%p,1ll*Inv*(now.b+1)%p};
} 
#define cnt(x) __builtin_popcount(x)
int main(){
    n=read();int Q=read();Root=read();
    inv[0]=inv[1]=1;
    for(rt i=2;i<=20;i++)inv[i]=1ll*inv[p%i]*(p-p/i)%p;
    for(rt i=1;i<n;i++){
        x=read();y=read();
        add(x,y);
        add(y,x);
        d[x]++;d[y]++;
    }
    for(rt i=1;i<(1<<n);i++){
        ret ans=dfs(Root,Root,i);
        Min[i]=ans.b*(cnt(i)&1?1:-1);
    }
    for(rt i=0;i<n;i++)
    for(rt j=0;j<1<<n;j++)if(j>>i&1)(Min[j]+=Min[j^(1<<i)])%=p;
    while(Q--){
        x=read();int sum=0;
        for(rt i=1;i<=x;i++)
        sum|=(1<<read()-1);
        writeln((Min[sum]+p)%p);
    }
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM