Game on Sum--组合数学--DP
\(Codeforces-Round 767\) (Div. 2) F2. \(Game \ on \ Sum\)
题意
\(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\) 个减。
则方程为:
显然
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做法衔接一下 ):
注意两个 -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' ;
}
}