[BZOJ5339]-[TJOI2018]教科书般的亵渎-自然数幂和

说在前面

me讨厌数学题!
讨厌讨厌讨厌死了!


题目

BZOJ5339传送门
看题可进传送门


解法

题目描述有点不清真
其实稍微想一想就能发现,这个题就是要我们求自然数幂和
假设我们已经会求自然数幂和了,这道题大概就这样做:枚举当前是第几次亵渎,记亵渎之前最高血量为 q ,把 q 拿出来求一次幂和,然后把那些不存在的怪的贡献用快速幂减掉。这样复杂度就是 Θ ( m 2 log 2 m + m δ ) δ 是指求幂和的复杂度

然后我们来考虑如何做幂和,我们要求的东西是 i = 0 n i m ,我们记这个东西为 S n m
看见 i m ,不难想到使用 斯特林数/二项式展开 等技巧,这里我们使用二项式展开
那么得到( C 是组合数)

S n m = i = 0 n i m = i = 0 n ( j = 0 m C m j ( i 1 ) m j ) = j = 0 m C m j ( i = 0 n ( i 1 ) m j ) = j = 0 m C m j S n 1 m j

我们貌似得到一个有点像递推式的东西,我们把 n 都加上 1 ,得到

S n + 1 m = j = 0 m C m j S n m j

但是我们发现它的下标变化了,这显然不是我们想要的
不难发现 S n + 1 m = S n m + ( n + 1 ) m ,把它和上面那个式子联立起来,于是递推式就只和 m 有关了,我们继续推导,得到:

S n m + ( n + 1 ) m = j = 0 m C m j S n m j S n m + ( n + 1 ) m = j = 1 m C m j S n m j + S n m ( n + 1 ) m = j = 2 m C m j S n 1 m j + C m 1 S n m 1

同时把m指标加1,得到
( n + 1 ) m + 1 = j = 2 m + 1 C m + 1 j S n 1 m + 1 j + C m + 1 1 S n m S n m = ( n + 1 ) m + 1 j = 2 m + 1 C m + 1 j S n 1 m + 1 j m + 1

然后我们成功地得到了一个递推式!这个递推式是 n 2 的,可以通过此题
注意,我们在推导过程中使用了 二项式展开 ,因此需要定义 0 0 = 1 ,这是由于 ( 0 1 + 1 ) 0 = i = 0 0 C 0 i ( 1 ) 0 = 1
并且在我们第一步推导的如下步骤中,当 m = j , i = 0 时,贡献也会变为 ( 1 ) 0 = 1 ,因此必须如上定义才能保证正确

j = 0 m C m j ( i = 0 n ( i 1 ) m j ) = j = 0 m C m j S n 1 m j


下面是代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

long long N , a[55] ;
int T , M , P = 1e9 + 7 , C[55][55] ;

long long s_pow( long long x , int b ){
    long long rt = 1 ;
    while( b ){
        if( b&1 ) rt = rt * x %P ;
        x = x * x %P , b >>= 1 ;
    } return rt ;
}

long long S[55] ;
long long cal( long long n , int m ){
    S[0] = n + 1 ; // 1 to n
    S[1] = ( 1 + n ) * n %P * s_pow( 2 , P - 2 )%P ;
    for( int i = 2 ; i <= m ; i ++ ){
        long long tmp = s_pow( n + 1 , i + 1 ) ;
        for( int j = 2 ; j <= i + 1 ; j ++ )
            tmp -= S[i-j+1] * C[i+1][j] %P ;
        tmp = tmp %P * s_pow( i + 1 , P - 2 ) %P ;
        S[i] = ( tmp + P )%P ;
    } return S[m] ;
}

void solve(){
    long long ans = 0 ;
    for( int i = 1 ; i <= M + 1 ; i ++ ){
        ans += cal( ( N - a[i-1] )%P , M + 1 ) ;
        for( int j = i ; j <= M ; j ++ )
            ans -= s_pow( a[j] - a[i-1] , M + 1 ) ;
    } printf( "%lld\n" , ( ans %P + P )%P ) ;
}

int main(){
    for( int i = 0 ; i <= 52 ; i ++ ){
        C[i][0] = 1 ;
        for( int j = 1 ; j <= i ; j ++ )
            C[i][j] = ( C[i-1][j] + C[i-1][j-1] )%P ;
    } scanf( "%d" , &T ) ;
    while( T -- ){
        scanf( "%lld%d" , &N , &M ) ;
        for( int i = 1 ; i <= M ; i ++ )
            scanf( "%lld" , &a[i] ) ;
        sort( a + 1 , a + M + 1 ) ;
        solve() ;
    }
}

猜你喜欢

转载自blog.csdn.net/izumi_hanako/article/details/80375054