zoukankan      html  css  js  c++  java
  • 【NOI OL #2】T3 游戏

    题目链接

    要求“恰好有$k$个回合不平局”,考虑先求“至少有$k$个回合不平局”。

    设$f[u][k]$为$u$的子树内,选出$k$对异色祖孙节点的方案数。树上背包走起!

    有两种情况,即$u$有没有算进去。先看$u$没被算进去的情况:$$f[u][k]=sum_{k_1+k_2+k_3+dots +k_p=k}prod f[son_1][k_1] imes f[son_2][k_2] imes f[son_3][k_3] imes f[son_p][k_p]$$

    不能直接枚举,考虑每次将子树合并:$$f[u][j+k]=sum_{son}f[son][j] imes f[u][k]$$

    这里注意枚举的上界是“已知最大异色点对数量”,可以保证单个子树的枚举时间是$O(n)$的。感性理解:每个点对仅会在$LCA$处贡献一次复杂度。

    同时为了避免覆盖的情况,需要额外使用一个一维数组。这里有一个很奇怪的问题,就是如果使用$memset$清零这个小数组,笔者找不到正确的长度表达式。见代码注释。

    2020.05.20更新:已经想到正确的长度表达式,只需要多加个一就好了(算上数组的第$0$位)。然而提交上去发现运算时间并没有明显的减少,甚至比不上评测机效率小幅度波动带来的影响。

    设$sum[u][c]$表示$u$子树内$c$颜色(仅有$0/1$)的点的数量,那么考虑$u$和子树内的异色点配对,有$$f[u][k]+=f[u][k-1] imes(\,sum[u][coplus 1]-(j-1)\,)quad(sum[u][coplus 1]>j-1)$$

    其中$oplus$表示异或。

    接下来设$G(k)=f[1][k] imes(m-k)!$,即在选出$k$个异色祖孙点对之后,剩下随意组合的方案总数,我们暂且认为它是至少选出了$k$对的方案总数。

    但是这样显然会算重,因为随意组合的点对中也会出现异色祖孙点对。设$H(i)$为恰好选出了$i$个异色祖孙点对的方案总数,可以发现:对于每个$H(i)$,它对$G(k)$的贡献为$ binom{i}{k}H(i)$。

    举个栗子,假设$i=4,k=3$,有①②③④四个异色祖孙点对。那么有可能是①②③被算在$f[1][k]$内,④是自由组合出来的;也有可能是①②④被算在$f[1][k]$内,③是自由组合出来的……总共有$ binom{i}{k}$种可能。

    于是乎$G(k)=sum_{i=k}^m binom{i}{k}H(i)$,可以进行二项式反演。二项式反演的公式:$$g(k)=sum_{i=k}^{infty} binom{i}{k}f(i)Leftrightarrow f(i)=sum_{k=i}^{infty}(-1)^{k-i} binom{k}{i}g(k)$$

    代入即可。

    $G(k)$并不是“至少选出了$k$对的方案总数”,因为它重复次数太多。笔者认为它起到一个桥梁的作用,方便我们使用二项式反演来求得答案。但不能否认,它内涵的“至少”的概念,和题目中给的“恰好”的概念相对,确实是我们解题的起手式。

    程序(100分):

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<map>
    #include<set>
    #define IL inline
    #define RG register
    #define _1 first
    #define _2 second
    using namespace std;
    typedef long long LL;
    const int N=5000;
    const LL mod=998244353;
    
        int n,m;
        bool col[N+3];
        
    struct Edge{
        int to,nxt;
    }e[(N<<1)+3];
        int h[N+3],top;
    
    IL void add(int u,int v){
        top++;
        e[top].to=v;
        e[top].nxt=h[u];
        h[u]=top;
        
    }
        
    IL LL add(LL x,LL y){return (x+y)%mod;}
    IL LL mul(LL x,LL y){return x*y%mod;}
    IL void fadd(LL &x,LL y){x=add(x,y);}
    IL void fmul(LL &x,LL y){x=mul(x,y);}
        
    IL LL qpow(LL a,LL b){
        LL ans=1;
        for(;b;fmul(a,a),b>>=1)
        if(b&1)
            fmul(ans,a);
        return ans;
        
    }
    
        LL fac[N+3],inv[N+3];
        
    IL void init(){
        fac[0]=fac[1]=inv[0]=inv[1]=1;
        for(int i=2;i<=m;i++)
            fac[i]=mul(fac[i-1],i);
            
        inv[m]=qpow(fac[m],mod-2);
        for(int i=m;i>2;i--)
            inv[i-1]=mul(inv[i],i);
        
    }
    
    IL LL C(int n,int m){
        return mul(mul(fac[n],inv[m]),inv[n-m]);
    }
    
        int sz[N+3],sum[N+3][2];                    //sz:子树内最大点对数 
        LL f[N+3][N+3],g[N+3];
        
    void dfs(int u,int fr){
        f[u][0]=1;
        for(int i=h[u];~i;i=e[i].nxt){
            int v=e[i].to;
            if(v==fr)    continue;
            
            dfs(v,u);
            sum[u][0]+=sum[v][0];
            sum[u][1]+=sum[v][1];
            
            for(int j=0;j<=sz[u]+sz[v];j++)    g[j]=0;
    //        memset(g,0,(sz[u]+sz[v])*(sizeof(LL)));
            for(int j=0;j<=sz[u];j++)
                for(int k=0;k<=sz[v];k++)
                    fadd(g[j+k], mul(f[u][j],f[v][k]) );
            
            sz[u]+=sz[v];
            for(int j=0;j<=sz[u];j++)
                f[u][j]=g[j];
            
        }
        sum[u][col[u]]++;
        
        for(int i=sz[u];i>=0;i--)
        if(sum[u][col[u]^1]-i>0)
            fadd(f[u][i+1], mul(f[u][i],sum[u][col[u]^1]-i) );
        if(f[u][sz[u]+1])
            sz[u]++;
        
    }
    
    int main(){
        freopen("p2.in","r",stdin);
        freopen("p2.out","w",stdout);
    
        scanf("%d
    ",&n);
        m=n>>1;
        for(int i=1;i<=n;i++)
            col[i]=getchar()-'0';
        memset(h,-1,sizeof h);
        top=-1;
        for(int i=1;i<n;i++){
            int u,v;    scanf("%d%d",&u,&v);
            add(u,v);    add(v,u);
            
        }
        
        init();
        
        memset(sz,0,sizeof sz);
        memset(sum,0,sizeof sum);
        memset(f,0,sizeof f);
        dfs(1,0);
        
        for(int i=0;i<=sz[1];i++)
            fmul(f[1][i],fac[m-i]);
        for(int k=0;k<=m;k++){
            LL ans=0,t=1;
            for(int i=k;i<=sz[1];i++,fmul(t,mod-1))
                fadd(ans, mul(t, mul(C(i,k),f[1][i]) ) );
            printf("%lld
    ",ans);
            
        }
    
        return 0;
    
    }
    View Code
  • 相关阅读:
    函数式编程语言
    Http
    小解_beginthreadex与_beginthreadex和CreateThread的区别
    Ring0句柄表遍历
    异步读写(ReadFileEx和ReadFile)之overlapped
    异步读写之利用完成历程
    windows核心编程第17章 一个文件两个缓存
    windows核心编程第17章 一个文件一个缓存
    windows核心编程第17章 一个文件 0个缓存
    进程间通信之利用CreateFilemapping()
  • 原文地址:https://www.cnblogs.com/Hansue/p/12897249.html
Copyright © 2011-2022 走看看