zoukankan      html  css  js  c++  java
  • [AtCoder ARC061F]Card Game for Three 组合数好题

    题目链接

    总结:组合数

    这$F$题好难啊...只会部分分做法,下面两个方法都是部分分做法。满分做法我去看看...会的话就补一下

    部分分做法

    方法1:

    首先$A$能赢的条件很明显,假设在所有的牌里面取出$A$张$A$牌,$B$张$B$牌,$C$张$C$牌,那么$A$能赢当且仅当$A=n,B<m,C<k$

    所以假设我们在拿出了$n$张$A$牌的情况下,中间穿插着拿了$B$张$B$牌,$C$张$C$牌,则有

    $$sum_{i=0}^{i<=m+k}C(n-1,i+n-1)*3^{m+k-i}*sum_{j=0}^{j<=m,j<=k}C(i,j)$$

    首先在$i+n-1$张牌中取$n-1$张$A$牌的方案为$C(n-1,n+i-1)$

    注意:最后一张牌一定需要是$A$,所以就只能有$n-1$

    而且剩下的牌数的排列为$3^{m+k-i}$,要乘上去

    以及在$i+n-1$中$B$和$C$的排列为$C(i,j)$,也要乘起来

    所以就得到了上面的公式

    但是,上面的做法我打挂了...这个式子是对的,我算后面那个$sum_{j=0}^{j<=m,j<=k}C(i,j)$那里我没有枚举好...查不出来啊...

    我在理解了满分做法之后终于发现我挂在哪里了...

    在枚举后面的东西的时候我没有分类讨论。但是改完后貌似还分少了一类...

    $subtask_1$10个点我错了$2$个...然后$subtask_2$被我强行水过去$2$个点..

    用$C$牌数量来分类:分为$j<k$,$j<m$,$j<i$(第三类我没分...不想去改了...)

    对于第一种情况:$x=sum_{j=0}^{j<k}C(i,j)$

    对于第二种情况:$x=sum_{j=0}^{j<m}C(i,k)$

    对于第三种情况:请读者独立思考(知道有第三种情况是因为我去看了一下满分的做法...)

    #include <bits/stdc++.h>
    
    using namespace std ;
    
    #define N 5010
    #define ll long long
    const int mod = 1e9 + 7 ;
    
    int n , m , k ;
    ll fac[ N ] , ifac[ N ] ;
    
    ll power( ll a ,ll b ) {
        ll base = a , ans = 1 ;
        while( b ) {
            if( b&1 )  ans = ( ans * base ) % mod ;
            base = ( base * base ) % mod ;
            b >>= 1 ;
        }
        return ans ;
    }
    ll mul( ll x ,ll y ) {
        return ( 1ll * x * y ) % mod ;
    }
    
    ll inv( ll x ) {
        return power( x , mod - 2 ) % mod ;
    }
    
    int main() {
        scanf( "%d%d%d" , &n , &m , &k ) ;
        
        fac[ 0 ] = 1 ;
        for( int i = 1 ; i < N ; i ++ ) fac[ i ] = mul( fac[ i - 1 ] , i ) ;
        for( int i = 0 ; i < N ; i ++ ) ifac[ i ] = inv( fac[ i ] ) ;
        
        ll ans = 0 , sum = n + k + m ;
        
        for( int i = n ; i <= sum; i ++ ) {// 取出n张A牌 
            for( int j = 0 ; j <= m ;j ++ ) {//B牌数量 
                int C = i - n - j ;//C牌数量 
                if( C < 0 || C > k ) continue ;
                ll tmp = mul( fac[ i - 1 ] , mul( ifac[ n - 1 ] , mul( ifac[ j ] ,ifac[ C ] ) ) ) ;
                tmp = mul( tmp , power( 3 , sum - i ) ) ;
                ans = ( ans + tmp ) % mod ;
            }
        }
        
        printf( "%lld
    " , ans ) ;
        
    }
    部分分做法1(有一定错误...)

    方法2:

    换一种想法,在前$i$张卡片中拿出$n$张$A$,$j$张$B$,$C$张$C$

    则可以推出一个公式

    $$sum_{i=0}^{i<=n+m+k}frac{(i-1)!}{(n-1)!j!C!}3^{n+k+m-i}$$

    如果看得懂那个方法一的话这个公式大概也是看得懂的吧...

    就是$i-1$的全排列数除掉$n-1$的全排列数和$j$的全排列数和$C$的全排列数

    这个是很基础的一个组合数的常识,这样子得到的就是我们要的当前情况的方案数,记住也要把当前剩下的那些的方案数也乘上去,即$3^{n+m+k-i}$

    然后这里的除法是$mod$ $m$意义下的,所以要求一下逆元,代码中的$fac[i]$即为$i!$的值,$ifac[i]$即为$i!$的逆元

    #include <bits/stdc++.h>
    
    using namespace std ;
    
    #define N 5010
    #define ll long long
    const int mod = 1e9 + 7 ;
    
    int n , m , k ;
    ll fac[ N ] , ifac[ N ] ;
    
    ll power( ll a ,ll b ) {
        ll base = a , ans = 1 ;
        while( b ) {
            if( b&1 )  ans = ( ans * base ) % mod ;
            base = ( base * base ) % mod ;
            b >>= 1 ;
        }
        return ans ;
    }
    ll mul( ll x ,ll y ) {
        return ( 1ll * x * y ) % mod ;
    }
    
    ll inv( ll x ) {
        return power( x , mod - 2 ) % mod ;
    }
    
    int main() {
        scanf( "%d%d%d" , &n , &m , &k ) ;
        
        fac[ 0 ] = 1 ;
        for( int i = 1 ; i < N ; i ++ ) fac[ i ] = mul( fac[ i - 1 ] , i ) ;
        for( int i = 0 ; i < N ; i ++ ) ifac[ i ] = inv( fac[ i ] ) ;
        
        ll ans = 0 , sum = n + k + m ;
        
        for( int i = n ; i <= sum; i ++ ) {// 取出n张A牌 
            for( int j = 0 ; j <= m ;j ++ ) {//B牌数量 
                int C = i - n - j ;//C牌数量 
                if( C < 0 || C > k ) continue ;
                ll tmp = mul( fac[ i - 1 ] , mul( ifac[ n - 1 ] , mul( ifac[ j ] ,ifac[ C ] ) ) ) ;
                tmp = mul( tmp , power( 3 , sum - i ) ) ;
                ans = ( ans + tmp ) % mod ;
            }
        }
        
        printf( "%lld
    " , ans ) ;
        
    }
    部分分做法2

    钻研了很久的题解,终于差不多搞懂满分做法了...

    满分做法

    方法一确实是对的。这个满分做法就是用来改进那里的

    观察耗时,耗时基本都是花费在枚举后面那一段,所以考虑优化那一段...(这个做法很清奇...正常写组合数不应该是优化那个式子吗...)

    $$sum_{i=0}^{i<=m+k}C(n-1,i+n-1)*3^{m+k-i}*sum_{j=0}^{j<=m,j<=k}C(i,j)$$

    还是这个式子,我们来优化掉后面那个$sum_{j=0}^{j<=m,j<=k}C(i,j)$(就是我写错了还查不出来的那个玩意...不!我写到这里的时候忽然发现我在枚举的时候没有分类讨论!)

    我们把后面那个$sum$拆掉,分三部分讨论

    假设我们现在手上有$x$张$C$牌

    当$x<k$时,随便取...(C的数量都比你要取的多了所以肯定不会超限)即$sum_{i=0}^{x<k}C(x,i)$

    当$x<m$时,$C$的数量在$i$以内,同时不能取超过$k$个,即$sum_{i=0}^{i<m}C(x,i)$

    当$m<=x<m+k$时,$B,C$数量均不超过$i$,同时$B$不能取超过$m$个,$C$不能取超过$k$个,即$sum_{i-m+1}^{i<k}C(x,i)$

    然后怎么优化呢

    如果你熟知杨辉三角这个东西的话,大概就会知道怎么优化了

    假设我们已经知道了$i-1$时$x$的值,那么其实可以推出下面的几个式子:

    情况$1$:$x=x*2$

    情况$2$:$x=x*2-C(i,k)$

    情况$3$:$x-x*2-C(i,k)-C(i,m)$

    这个挺容易推的吧...如果部分分做法的第一种有推出来那么这个优化也就顺理成章了的样子(但是我推出来了一半...)

    #include <bits/stdc++.h>
    
    using namespace std ;
    
    const int mod = 1e9 + 7 ;
    const int N = 900010 ;
    #define ll long long 
    
    int n , m , k ;
    ll fac[ N ] , ifac[ N ] , p[ N ] ; 
    
    ll mul( ll x , ll  y ) {
        return ( 1ll * x * y ) % mod ;
    }
    
    ll add( ll x , ll y ) {
        return ( x + y ) % mod ;
    }
    
    ll power( ll a , ll b ) {
        int ans = 1 , base = a ;
        while( b ) {
            if( b&1 ) ans = mul( ans , base ) ;
            base = mul( base , base ) ; 
            b >>= 1 ;
        }
        return ans ;
    }
    
    ll inv( ll x ) {
        return power( x , mod - 2 ) % mod ;
    }
    
    ll C( ll x , ll y ) {
        return ( fac[ x ] * ifac[ y ] % mod * ifac[ x - y ] % mod ) % mod ;
    }
    
    int main() {
        scanf( "%d%d%d" , &n , &m , &k ) ;
        fac[ 0 ] = 1ll ;
        p[ 0 ] = 1ll ;
        for( int i = 1 ; i < N ; i ++ ) {
            fac[ i ] = fac[ i - 1 ] * i % mod ;
            p[ i ] = p[ i - 1 ] * 3ll % mod ;
        }
        for( int i = 0 ; i < N ; i ++ ) {
            ifac[ i ] = inv( fac[ i ] ) ;
        }
        ll ans = 0 , x = 1ll ;
        n -- ;
        if( m < k ) swap( m , k ) ;
        for( int i = 0 ; i <= m + k ; i ++ ) {
            ans = ( ans + C( n + i , n ) * p[ m + k - i ] % mod * x )  % mod  ;
            /*
            for( int j = 0 ; j <= min( i , m  ) ; j ++ ) {
                if( i - j > k || i - j < 0 ) break ;
                x = add( x , C( i , j ) ) ;
            }
            */
            if( i < k ) x = ( x * 2ll ) % mod ;
            else if( i < m ) x = ( x * 2ll - C( i , k ) ) % mod ;
            else  x = ( x * 2ll - C( i , k ) - C( i , m ) ) % mod ;
        }
        printf( "%lld
    " , add( ans , mod ) ) ;
        return 0 ;
    } 
    View Code

    数学题真的虐哭我...这题我研究了$3$天...

    不过确实是一道组合数的好题...做完后觉得对组合数这玩意的理解更深了一些...

  • 相关阅读:
    用rpm安装软件的常用步骤
    将应用发布到WasLiberty的两种方法
    安装 ibm-java-x86_64-sdk-6.0-9.3.x86_64.rpm 的三步骤
    人是科技的第一生产力。不重视人的价值,不尊重人的需求,不解放人的生产力,必将被互联网时代快速淘汰。
    Java保存简单偏好的类
    实用快捷键Win+L=锁屏
    判断一件事有无技术含量的标准
    主动去平事 别等事找人
    看了某些蛊惑人心的招聘广告,实在忍不住想要提醒那些跃跃欲奉献的后生们
    ubuntu16.04在英文状态下安装中文语言包的过程(法二:命令行的方式)
  • 原文地址:https://www.cnblogs.com/henry-1202/p/ARC061F.html
Copyright © 2011-2022 走看看