2017.10.20(积性函数,BSGS,K进制数位DP)

本文解析了一套数论竞赛题,包括积性函数、BSGS算法及卢卡斯定理在数位DP中的应用。详细介绍了每道题目的解题思路与关键实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说在前面

看见大家都在写总结,决定也学着写一写
最后的20天一定要好好的过啊!

ps:这个markdown编辑器对Microsoft Edge也太不友好了…一打字页面就会乱跳,光标定位不准确,各种…神烦


总括

整套题都是数论题,结合上一套题大概是把数论常考的地方覆盖了一遍。

第一道题是积性函数,如果想得起来

d|nϕ(d)==n
这个式子,然后又能yy出积性的话,就是一个简单题了。

第二道题是BSGS裸题,因为很久没写了,考场YY两个半小时总算写出来。结果被求逆元坑死掉了。快速幂求逆元只有在mod为质数才能用(因为原理是费马小定理),而exgcd求逆元在任何情况都可以用(当然前提是存在逆元)。

第三题用卢卡斯定理处理一下,就是一个五进制的简单数位DP,细节有些多。


T1

这里写图片描述
观察这个函数,首先根据公式,左半边那一坨可以得到:

(d|nϕ(d))m =nm
这个可以在for的时候直接搞,因为n只有1e7。
接下来右半边那一坨,可以发现是一个卷积形式,自己手推n为p(质数)的几次方的形式,可以发现:
d|nσ(d)μ(nd)nd=k(k1)n     (n==pk)

又因为他是积性的,可以直接线性筛出来。
最后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 ) ;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值