• LOJ #2542「PKUWC2018」随机游走


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

    $ PKUWC$滚粗后这题一直在$ todolist$里

    今天才补掉..还要更加努力啊..

    LOJ #2542

    题意:给一棵不超过$ 18$个节点的树,$ 5000$次询问,每次问从根随机游走走遍一个集合的期望步数


    $ Solution:$

    考虑$ Min$-$Max$容斥

    有$ Max(S)=sumlimits_{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('
    ');}
    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;
    }
  • 相关阅读:
    Java语言
    Java面向对象编程思想
    final关键字
    abstract关键字
    对象初始化过程
    访问修饰符
    继承
    面向对象设计
    static
    封装
  • 原文地址:https://www.cnblogs.com/DreamlessDreams/p/10040092.html
Copyright © 2020-2023  润新知