1. 程式人生 > >[AtCoder ARC061F]Card Game for Three 組合數好題

[AtCoder ARC061F]Card Game for Three 組合數好題

排列 出了 情況 turn tasks bits 而且 如果 ref

題目鏈接

總結:組合數

這$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\n" , 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\n" , 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 -- ;
    for( int i = 0 ; i <= m + k ; i ++ ) {
        ans = ( ans + C( n + i , n ) * p[ m + k - i ] % mod * x )  % mod  ;
        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\n" , add( ans , mod ) ) ;
    return 0 ;
} 
滿分做法

數學題真的虐哭我...這題我研究了$3$天...

不過確實是一道組合數的好題...做完後覺得對組合數這玩意的理解更深了一些...

[AtCoder ARC061F]Card Game for Three 組合數好題