• Atcoder arc093F 状压DP+容斥原理


    有 $2^n$ 名选手,编号为 $1$ 至 $2^n$. 现在这 $2^n$ 名选手将进行 $n$ 轮淘汰赛,决出胜者. 若 $x<y$,则 $x$ 能战胜 $y$.

    但是有 $m$ 个例外,1 号选手会输给这 $m$ 个选手,问有多少种初始排列方式使得 $1$ 号点能取得胜利.

    (每次是 2i vs 2i-1,即 1 vs 2,3 vs 4,5 vs 6........被淘汰掉的选手会从序列里出来,然后其余按照相对顺序继续 pk)

    题解:

    手画一下,发现这 $2^n$ 名选手的对阵图是一个二叉树结构.

    而二叉树中每个非叶子节点代表着子树最小值.

    然后你发现对于二叉树中每一个非叶子节点,左右儿子是可以任意交换的.

    我们不妨就钦定编号为 1 的在第一个位置上(最后再乘上一个可以随意左右交换的 $2^n$ 即可)

    然后你发现 1 号点在二叉树的最靠左的叶子上,那么如果 1 号点想胜利(爬到根),就要满足所有与 1 pk 的区间不能含有那 $m$ 个数.

    而与 1 号点 pk 的区间就是 $[2,2]$,$[3,4]$,$[5,8]$,$[9,16]$......

    我们的目的就是把 $2$ ~ $2^n$ 这些数填到这些区间中,使得 1 能赢.

    但是你发现正着求非常困难,所以我们考虑用容斥来解决.

    套路:即钦定一些区间一定不合法,其余区间随便填,最后来一个二项式反演.

    令 $f[i][S]$ 表示从大到小已考虑 $i$ 个数,且集合 $S$ 不合法的方案数.

    这个用状压dp 的转移方式转移一下就可以了.

    #include <cstdio> 
    #include <algorithm>      
    #define N 18   
    #define ll long long 
    #define mod 1000000007 
    #define setIO(s) freopen(s".in","r",stdin) 
    using namespace std;        
    int bin[N],f[N][1<<N],fac[1<<N],inv[1<<N],a[N];    
    int qpow(int x,int y) 
    {
        int tmp=1; 
        for(;y;y>>=1,x=(ll)x*x%mod) 
            if(y&1) tmp=(ll)tmp*x%mod; 
        return tmp; 
    }
    int C(int x,int y) { if(x<y) return 0; return (ll)fac[x]*inv[y]%mod*inv[x-y]%mod; }    
    int count(int x) 
    { 
        int c=0;  
        while(x) { if(x&1) ++c;  x>>=1; }   
        return c;  
    }
    int main() 
    { 
        // setIO("input");  
        int i,j,n,m;  
        scanf("%d%d",&n,&m);                                    
        bin[0]=fac[0]=inv[0]=1; 
        for(i=1;i<=n;++i) bin[i]=bin[i-1]<<1;         
        for(i=1;i<=bin[n];++i) fac[i]=(ll)fac[i-1]*i%mod,inv[i]=qpow(fac[i],mod-2);
        for(i=1;i<=m;++i) scanf("%d",&a[i]);      
        sort(a+1,a+1+m); 
        f[m+1][0]=1;    
        int cnt=0; 
        for(i=m;i>=1;--i) 
        {
            for(j=0;j<bin[n];++j) 
            {
    
                f[i][j]=(ll)(f[i][j]+f[i+1][j])%mod;      
                int t=bin[n]-1-j;  // 共有 t 个空位置                    
                for(int k=0;bin[k]<bin[n];++k) 
                {  
                    if(j&bin[k]||!f[i+1][j]) continue;        
                    (f[i][j|bin[k]]+=((ll)f[i+1][j]*C(t-a[i]+1,bin[k]-1)%mod*fac[bin[k]]%mod)%mod)%=mod;  
                }         
            }
        }     
        int ans=0;   
        for(i=0;i<bin[n];++i) 
        {
            int d=(count(i)&1)?mod-1:1;          
            ans=(ll)(ans+(ll)f[1][i]*fac[bin[n]-1-i]%mod*d%mod)%mod;   
        }    
        ans=(ll)ans*bin[n]%mod;   
        printf("%d
    ",ans); 
        return 0;
    }
    

      

  • 相关阅读:
    第四周作业
    第三周作业
    第二周作业
    20162325 2016-2017-2 《程序设计与数据结构》课程总结
    实验五 网络编程与安全 实验报告
    实验四 Android程序设计-5
    结对编程项目-四则运算 挑战出题
    20162325金立清 实验四 Android程序设计 实验报告
    20162325 结对编程项目-四则运算 整体总结
    实验三 敏捷开发与XP实践 实验报告
  • 原文地址:https://www.cnblogs.com/guangheli/p/12162164.html
Copyright © 2020-2023  润新知