说在前面
看见大家都在写总结,决定也学着写一写
最后的20天一定要好好的过啊!
ps:这个markdown编辑器对Microsoft Edge也太不友好了…一打字页面就会乱跳,光标定位不准确,各种…神烦
总括
整套题都是数论题,结合上一套题大概是把数论常考的地方覆盖了一遍。
第一道题是积性函数,如果想得起来
第二道题是BSGS裸题,因为很久没写了,考场YY两个半小时总算写出来。结果被求逆元坑死掉了。快速幂求逆元只有在mod为质数才能用(因为原理是费马小定理),而exgcd求逆元在任何情况都可以用(当然前提是存在逆元)。
第三题用卢卡斯定理处理一下,就是一个五进制的简单数位DP,细节有些多。
T1
观察这个函数,首先根据公式,左半边那一坨可以得到:
接下来右半边那一坨,可以发现是一个卷积形式,自己手推n为p(质数)的几次方的形式,可以发现:
又因为他是积性的,可以直接线性筛出来。
最后for一遍统计即可。代码我就没有写了【懒如我】
T2
这就是一个BSGS裸题(BSGS这个名字起的真的好,baby step giant step,大步小步走,我就是靠这个yy出来的。小步每次走一倍,大步每次走根号倍然后去哈希表里查询)
实现细节注意求逆元的时候不能用快速幂!因为没有保证mod是素数【被这个坑掉80分,哭叽叽】
代码如下
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
long long t , mmod ;
int T , phi , p[100005] , topp , isnotp[100005] , pdiv[205] , pcnt ;
struct Table{
int num[100005] , k[100005] , head[100005] , pre[100005] , tp ;
void Init( ){
tp = 0 ;
memset( head , 0 , sizeof( head ) ) ;
}
int Query( long long x ){
int id = x %100000 ;
for( int i = head[id] ; i ; i = pre[i] )
if( num[i] == x ) return k[i] ;
return -1 ;
}
int Insert( int fc , long long x ){
int id = x %100000 ;
num[++tp] = (int)x ; k[tp] = fc ;
pre[tp] = head[id] ;
head[id] = tp ;
}
}LT ;
long long s_pow( long long x , int b ){
long long rt = 1 ;
while( b ){
if( b&1 ) rt = rt * x %mmod ;
x = x * x %mmod ; b >>= 1 ;
}
return rt ;
}
void getPrime(){
for( int i = 2 ; i <= 100000 ; i ++ ){
if( !isnotp[i] )
p[++topp] = i ;
for( int j = 1 ; j <= topp && i * p[j] <= 100000 ; j ++ ){
isnotp[ i*p[j] ] = true ;
if( i%p[j] == 0 ) break ;
}
}
}
void P_div( int x ){
pcnt = 0 ;
//printf( "(pdiv In %d)\n" , x ) ;
for( int i = 1 ; i <= topp && x != 1 ; i ++ )
if( x % p[i] == 0 ){
pdiv[++pcnt] = p[i] ;
//printf( "(pdiv ) %d\n" , p[i] ) ;
while( x % p[i] == 0 ) x /= p[i] ;
}
if( x != 1 ) pdiv[++pcnt] = x ;
}
void getPhi(){
phi = mmod ;
for( int i = 1 ; i <= pcnt ; i ++ )
phi = 1LL * phi * ( pdiv[i] - 1 ) / pdiv[i] ;
}
void exgcd( long long a , long long b , long long &x , long long &y ){
if( !b ){
x = 1 ; y = 0 ;
} else {
long long xx , yy ;
exgcd( b , a%b , xx , yy ) ;
x = yy ; y = xx - a / b * yy ;
}
}
long long getinv( long long a , long long m ){
long long x , y ;
exgcd( a , m , x , y ) ;
return ( x%m + m ) %m ;
}
void BSGS(){
int q = ceil( sqrt( phi ) ) ;
long long bs = 1 , gs , aim = 1 ;
for( int i = 0 , ans ; i < q ; i ++ ){
ans = LT.Query( bs ) ;
if( ans == -1 ) LT.Insert( i , bs ) ;
else{
printf( "%d\n" , i ) ;
return ;
}
bs = bs * t %mmod ;
}
gs = getinv( bs , mmod ) ;
for( int a = 1 , ans ; a <= q ; a ++ ){
aim = aim * gs %mmod ;
ans = LT.Query( aim ) ;
if( ans != -1 ){
printf( "%d\n" , a * q + ans ) ;
return ;
}
}
printf( "1\n" ) ;
}
int main(){
getPrime() ;
scanf( "%d" , &T ) ;
while( T -- ){
scanf( "%I64d%I64d" , &t , &mmod ) ;
P_div( mmod ) ;
getPhi() ;
LT.Init() ; BSGS() ;
}
}
T3
题目要求在模1e9+7下,统计5的倍数。
因为输入保证了组合数算出来的值大于0,所以题等价于统计这些组合数在模5意义下有多少个0。由卢卡斯定理,我们可以将C(n,m)按照五进制分解,判断每一位分解出来的C(ni,mi)中,mi是否大于ni,如果大于,那么这一位计算出来将会是0。又因为卢卡斯分解之后,各项之间是乘法关系,因此分解出来的只要有一个0,就表示这个C(n,m)可以被5整除。接下来就是数位DP了。
代码如下
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int Ndiv[31] , T , Ncnt , tdiv[31] , tcnt ;
long long ans = 0 , dp[31][2] , L , R , N ;
void div( long long x , int *w , int &cnt ){
cnt = 0 ;
while( x ){
w[++cnt] = x%5 ;
x /= 5 ;
}
}
long long dfs( int dep , bool zero , bool limit ){
if( dep == 0 ) return zero ;
if( !limit && dp[dep][zero] != -1 ) return dp[dep][zero] ;
int lim = ( limit ? tdiv[dep] : 4 ) ;
long long rt = 0 ;
for( int i = 0 ; i <= lim ; i ++ )
rt += dfs( dep-1 , zero || ( i > Ndiv[dep] ) , limit && ( i == lim ) ) ;
if( !limit ) dp[dep][zero] = rt ;
return rt ;
}
int main(){
// freopen( "ccount.in" , "r" , stdin ) ;
// freopen( "ccount.out", "w" , stdout) ;
scanf( "%d" , &T ) ;
while( T -- ){
for( int i = 0 ; i <= 30 ; i ++ )
dp[i][0] = dp[i][1] = -1 ;
scanf( "%I64d%I64d%I64d" , &L , &R , &N ) ;
div( N , Ndiv , Ncnt ) ;
div( R , tdiv , tcnt ) ;
ans = dfs( tcnt , false , true ) ;
if( L ){
div( L - 1 , tdiv , tcnt ) ;
ans -= dfs( tcnt , false , true ) ;
}
printf( "%I64d\n" , ans ) ;
}
}