Game on Sum--组合数学--DP

\(Codeforces-Round 767\) (Div. 2) F2. \(Game \ on \ Sum\)

\(HERE\)

题意

\(QZS\)\(HANGRY\) 玩游戏。

游戏共有 \(n\) 轮, 对于整一局游戏给定一个 \(K\) , 游戏的过程是在改变一个数 \(a\) 的值 , 初始为 \(1\) .

在每一轮中 \(QZS\) 会在 \([1 , k]\) 的区间里取出一个 \(\bf{实数}\) , \(HANGRY\) 选择用 \(a\) 加上这个实数还是减去这个实数 , \(HANGRY\) 的目的是让 \(a\) 最小 , \(QZS\) 相反.

在这 \(n\) 轮中 , \(HANGRY\) 至少选择 \(m\) 轮选择加 .

\(HANGRY\)\(QZS\) 都是绝顶聪明的 , 这意味着他们会选择最优解 .

问最后的 \(a\) 是多少 . 多测.答案对 \(1e9+7\) 取模.

数据范围:

$n \le 10^6 , m \le n , T \le 5 \times 10^5 \sum n = 10^6 $

\(\sum n\) 为每个测试点所有测试数据中 \(n\) 的和.

题解

\(O(nm)\) (<100 pts)

考虑 \(DP\)

\(dp_{i,j}\) 表示长度为 \(i\) , 选了 \(j\) 个减。

则方程为:

\[dp_{i , j} = \frac{dp_{i - 1 , j} + dp_{i - 1 , j - 1}}{2} \]

显然

code

114514
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 2e3 + 10 ; 
const int mod = 1e9 + 7 ; 

int dp[N][N] , n , T , m , K ; 
int nueyong ; 

inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 

    while ( c < '0' || c > '9' ) {

        if ( c == '-' ) f = -f ; 

        c = getchar() ; 
    }

    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }

    return x * f ; 
}

inline int Regular_Quick_Pow( int a , int b ) {
    int ans = 1 ; 

    while ( b > 0 ) {

        if ( b & 1 ) ans = ( ans * a ) % mod ; 

        b >>= 1 ; a = ( a * a ) % mod ; 
    }

    return ans ; 
}

signed main() {

    #ifndef ONLINE_JUDGE
        freopen( "1.in" , "r" , stdin ) ; 
        freopen( "1.out" , "w" , stdout ) ; 
    #endif

    T = read() ; 

    nueyong = Regular_Quick_Pow( 2 , mod - 2 ) ; 

    while ( T -- ) {
        
        n = read() , m = read() , K = read() ; 
        dp[1][0] = K , dp[1][1] = 0 ; 

        for ( int i = 2 ; i <= n ; ++ i ) {
            
            dp[i][0] = ( i * K ) % mod ; 

            for( int j = 1 ; j <= min(i - 1 , n - m) ; ++ j ) {

                dp[i][j] = ( ( ( dp[i - 1][j - 1] + dp[i - 1][j] ) % mod ) * nueyong ) % mod ; 

            }

        }

        cout << dp[n][n - m] << '\n' ; 
    }
}

组合方法

我们目前把这个东西写出来

    1     0     0     0     0     0     0     0     0     0     0 
    4     1     0     0     0     0     0     0     0     0     0 
   12     5     1     0     0     0     0     0     0     0     0 
   32    17     6     1     0     0     0     0     0     0     0 
   80    49    23     7     1     0     0     0     0     0     0 
  192   129    72    30     8     1     0     0     0     0     0 
  448   321   201   102    38     9     1     0     0     0     0 
 1024   769   522   303   140    47    10     1     0     0     0 
 2304  1793  1291   825   443   187    57    11     1     0     0 
 5120  4097  3084  2116  1268   630   244    68    12     1     0 

对于点 \(i , j\) 来说, 答案是 \(\frac{dp_{i , j}}{2^{i - 1}} \times K\)

我们考虑点 \((i , j)\) , 使他用第 \(0\) 列表示出来,我们摆出来一个杨辉三角,来看看:

  1
  1  1 
  1  2  1
  1  3  3  1
  1  4  6  4  1
  1  5 10 10  5  1

我们能够发现,将 \(i , j\) 看做 \(0 , 0\) 他下面的选的次数可以用杨辉三角的某个点表示。

并且为(点设为 \((n , n - m)\) , 与上面我们的dp做法衔接一下 ):

\[ans = \sum_{i = 1}^{n - 1}C^{n - m - 1}_{n - i - 1} \times dp_{i , 0}\times K \]

注意两个 -1 , 关于这个位置为啥要减一,这里解释一下:

对于当已经到 \(0\) 时,在杨辉三角中的位置依然可能有上一位转过来,但是两行的首位是没有关系的,那么他只能够由他的左上方的点转移过来,就相当于左上方的值了。

code

114514
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 2e6 + 10 ; 
const int mod = 1e9 + 7 ; 

int n , T , m , K ; 
int now[N] ; 

inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 

    while ( c < '0' || c > '9' ) {

        if ( c == '-' ) f = -f ; 

        c = getchar() ; 
    }

    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }

    return x * f ; 
}

namespace Combination {
    int D[N] ; int nueyong[N] , sum_neo[N] , sum[N] ; 

    inline void lear_neoyong() {

        sum_neo[0] = sum_neo[1] = 1 ; 
        nueyong[1] = 1 ; nueyong[0] = 1 ; 
        sum[0] = sum[1] = 1 ; 

        for( int i = 2 ; i < N ; ++ i ) {
            
            int p = mod ; 
            int k = p / i ; 
            nueyong[i] = ( k * ( p - nueyong[p % i] ) ) % p ; 
            sum_neo[i] = ( nueyong[i] * sum_neo[i - 1] ) % p ; 
            sum[i] = ( i * sum[i - 1] ) % p ; 
        
        }
    }

    int Quick_Pow( int alpha , int beta )
    {
        int ans = 1 ; 

        while ( beta > 0 ) { 
            
            if( beta & 1 ) ans = ( ans * alpha ) % mod ; 
            
            beta >>= 1 ; alpha = ( alpha * alpha ) % mod ;  
        }
        
        return ans ; 
    }

    int Regular_C_of_Pow_Class( int n , int m ) {
        int alpha = 1 , beta = 1 , rereturn = 0 ; 
        
        if( m <= n && n >= 0 && m >= 0 ) {
            
            for( int i = n - m + 1 ; i <= n ; ++ i ) {
                alpha = ( alpha * i ) % mod ; 
            
            }
            for( int i = 1 ; i <= m ; ++ i ) {
                beta = ( beta * i ) % mod ; 
            }   
            
            rereturn = ( alpha * Quick_Pow( beta , mod - 2 ) ) % mod ;  
            return rereturn ; 
        
        }
        
        else return 0 ; 
    
    }

    inline int jc( int x ) {
        return sum[x] ; 
    }

    inline int neo_jc( int x ) {  
        
        if ( x == 0 ) return 1 ; 
        
        return sum_neo[x] ; 
    }

    int Regular_C_of_Inv( int n , int m ) {
        return ( ( ( jc( n ) * neo_jc( n - m ) ) % mod ) * neo_jc( m ) ) % mod ; 
    }

    int C_Lucas_Using_Inv( int n , int m ) {
        
        if ( m > n ) return 0 ; 

        if ( m == 0 ) return 1 ; 
        
        return ( Regular_C_of_Inv( n % mod , m % mod ) * C_Lucas_Using_Inv( n / mod , m / mod ) ) % mod ; 
    }

    int C_Lucas_Using_Pow( int n , int m ) {
        
        if( m == 0 ) return 1 ; 

        return ( Regular_C_of_Pow_Class( n % mod , m % mod ) * C_Lucas_Using_Pow( n / mod , m / mod ) ) % mod ; 
    }

    void Asking_for_Derangement() {

        D[0] = 1 ; 
        D[1] = 0 ; 
        D[2] = 1 ; 
        for( int i = 3 ; i < N ; ++ i ) {

            D[i] = ( i - 1 ) * ( D[i - 1] + D[i - 2] ) % mod ; 
        
        }
    }

    inline void Cleared() {
        memset( D , 0 , sizeof(D) ) ; 
        memset( sum_neo , 0 , sizeof(sum_neo) ) ; 
        memset( sum , 0 , sizeof(sum) ) ;  
        memset( nueyong , 0 , sizeof(nueyong) ) ; 
    }
} ; 
using namespace Combination ; 

inline int Regular_Quick_Pow( int a , int b ) {
    int ans = 1 ; 

    while ( b > 0 ) {

        if ( b & 1 ) ans = ( ans * a ) % mod ; 

        b >>= 1 ; a = ( a * a ) % mod ; 
    }

    return ans ; 
}

signed main() {

    #ifndef ONLINE_JUDGE
        freopen( "1.in" , "r" , stdin ) ; 
        freopen( "1.out" , "w" , stdout ) ; 
    #endif

    T = read() ; 
    lear_neoyong() ; 
    for ( int i = 1 ; i <= 1e6 * 2 ; ++ i ) {
        now[i] = ( i * Regular_Quick_Pow( 2 , i - 1 ) ) % mod ; 
    }

    while ( T -- ) {

        n = read() , m = read() , K = read() ; 

        if ( n - m == 0 ) {
            cout << ( n * K ) % mod << '\n' ; 
            continue ; 
        }

        int ans = 0 ; 
        for ( int i = 1 ; i <= n - 1 ; ++ i ) {

            ans = ( ans + C_Lucas_Using_Inv( n - i - 1 , n - m - 1 ) * now[i] ) % mod ; 
        }

        cout << ( ( ( K * ans ) % mod ) * Regular_Quick_Pow( Regular_Quick_Pow( 2 , n - 1 ) , mod - 2 ) ) % mod << '\n' ; 
    }
}

结尾撒花 \(\color{pink}{✿✿ヽ(°▽°)ノ✿}\)

posted @ 2024-03-20 17:33  HANGRY_Sol&Cekas  阅读(73)  评论(1编辑  收藏  举报