说在前面
me讨厌数学题!
讨厌讨厌讨厌死了!
题目
BZOJ5339传送门
看题可进传送门
解法
题目描述有点不清真
其实稍微想一想就能发现,这个题就是要我们求自然数幂和
假设我们已经会求自然数幂和了,这道题大概就这样做:枚举当前是第几次亵渎,记亵渎之前最高血量为
q
q
,把 拿出来求一次幂和,然后把那些不存在的怪的贡献用快速幂减掉。这样复杂度就是
Θ(m2log2m+m∗δ)
Θ
(
m
2
log
2
m
+
m
∗
δ
)
。
δ
δ
是指求幂和的复杂度
然后我们来考虑如何做幂和,我们要求的东西是
∑ni=0im
∑
i
=
0
n
i
m
,我们记这个东西为
Smn
S
n
m
看见
im
i
m
,不难想到使用 斯特林数/二项式展开 等技巧,这里我们使用二项式展开
那么得到(
C
C
是组合数)
我们貌似得到一个有点像递推式的东西,我们把
n
n
都加上 ,得到
但是我们发现它的下标变化了,这显然不是我们想要的
不难发现
Smn+1=Smn+(n+1)m
S
n
+
1
m
=
S
n
m
+
(
n
+
1
)
m
,把它和上面那个式子联立起来,于是递推式就只和
m
m
有关了,我们继续推导,得到:
然后我们成功地得到了一个递推式!这个递推式是
m2
m
2
的,可以通过此题
注意,我们在推导过程中使用了 二项式展开 ,因此需要定义
00=1
0
0
=
1
,这是由于
(0−1+1)0=∑0i=0Ci0(−1)0=1
(
0
−
1
+
1
)
0
=
∑
i
=
0
0
C
0
i
(
−
1
)
0
=
1
并且在我们第一步推导的如下步骤中,当
m=j,i=0
m
=
j
,
i
=
0
时,贡献也会变为
(−1)0=1
(
−
1
)
0
=
1
,因此必须如上定义才能保证正确
下面是代码
#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() ;
}
}