Sumdiv ( 求因子和+逆元)

13 篇文章 0 订阅
11 篇文章 0 订阅

题目

Consider two natural numbers A and B. Let S be the sum of all natural divisors of A^B. Determine S modulo 9901 (the rest of the division of S by 9901).

Input

The only line contains the two natural numbers A and B, (0 <= A,B <= 50000000)separated by blanks.

Output

The only line of the output will contain S modulo 9901.

题意

求a^b的因子数和%9901

具体分析

首先我们第一反应出

m为它的素因子个数

则就有

n^{k} = p_1{}^{^{w1*k}}p_{2}_{w2*k} *......*p_{t}^{wt*k}

t为它的素因子个数(就是上一句的m)

重点来了,我们需要知道:

这个公式可以求n的因子和

那么,等比数列怎么求呢?

百度一下就可知道:

好的,现在似乎我们就可以求出结果了,我们只需要把n改成n*b即可,再算快速幂

但是,你认为vjudge题的心机有这么简单吗

首先,我们如果直接求先不管时间

除了还要模,可以得到正确答案吗?

这里,很多大佬都用了

二分求等比数列和

可是,我才不会用这么拗口的名字求呢

竟然有除法和模运算,用逆元不就行了??

懒懒的我就是这样做的~~

信心满满地提交后:

然后,我就是这样的:

  

连极限数据都对了的,为什么呢?

我才不会告诉你们我是偷偷看了题解的呢

我们知道,只有

a*b%c=1 && gcd(a,c)= 1

时,才有逆元,但是题目并不能保证这个

万一有a是c的倍数呢

所以我们需要用到另一个公式:

\frac{a}{b}    mod c = a mod b*c   / b

证明:设a/b=tm+x

                 a=tbm+bx

                 a mod(bm) = bx

                 a mod(bm) /b =x

               所以 (a/b)mod m = amod(bm)/b

本以为逆元掌握得差不多的我竟然不知道!!!

你以为这样就完了吗,当然不可能!

我说过,vjudge是把我们编程社坑惨了滴

所以,我们在做前面p^{n*b+1}爆longlong了怎么办,如果n很大,确实有可能

所以我们还需要引进一个新知识:

快速乘

怎么做呢,其实二进制是满足乘法分配律的

举个栗子:

10 =(1010)2  ,4*13等于4*(1010)2 ,展开得到4*13 == 4*(1000+10)2

所以就有了:

ll qc( ll x , ll y , ll m ){
    ll sum = 0;
    while( y ){
        if( y % 2 ) sum = ( sum + x ) % m;
        x = ( x + x ) % m;
        y /= 2;
    }
    return sum;
}

这是logn的做法,当然还有o(1)的

ll qc(ll a, ll b, ll m){
    ll c = a * b - ( ll ) ( ( long double ) a * b / m + 0.5 ) * m;
    if( c < 0 )
        c += m;
    return c;
}

卡一次啊

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 7900;//可以算一下,只需要根号a的最大值以内的质数就行了
#define ll long long
const ll mod = 9901;
int pn;
ll a , b , prime[MAXN+3] , p[MAXN + 2];
bool vis[MAXN + 3];
ll sum[MAXN+2];
void pr(){//还是习惯用欧拉筛
    for( int i = 2 ; i <= MAXN ; i ++ ){
        if( !vis[i] ){
            prime[++pn] = i;
        }
        for( int j = 1 ;  j <= pn && 1ll * i * prime[j] <= MAXN ; j ++ ){
            vis[i*prime[j]] = 1;
            if( i % prime[j] == 0 )
                break;
        }
    }
}
ll qc(ll a, ll b, ll m){//快速乘
    ll c = a * b - ( ll ) ( ( long double ) a * b / m + 0.5 ) * m;
    if( c < 0 )
        c += m;
    return c;
}
ll qpow( ll x , ll y , ll m )//快速幂
{
    x %= m;
    ll sum = 1;
    while( y )
    {
        if( y % 2 )
            sum = qc( sum , x , m ) % m;
        x = qc( x , x , m ) % m;
        y /= 2;
    }
    return sum;
}
int main()
{
    pr();
    while( ~scanf( "%lld%lld" , &a , &b ) )
    {
        memset( sum , 0 , sizeof( sum ) );//习惯要养好
        memset( p , 0 , sizeof( p ) );
        ll cnt = 0;
        if( a == 0 )
        {
            printf( "0" );
            return 0;
        }//其实没啥用,防止万一
        for( int i = 1 ; prime[i] <= sqrt( 1.0 * a ) && i <= pn ; i++ )
        {//这里就是分解因数了
            if( a % prime[i] == 0 )
            {
                cnt ++;
                p[cnt] = prime[i];
                while( a % prime[i] == 0 )
                {
                    sum[cnt] ++;
                    a /= prime[i];
                }
            }
        }
        if( a > 1 ){//注意,这里不能打掉
            p[++cnt] = a;
            sum[cnt] ++;
        }
        ll ans = 1;
        for( int i = 1 ; i <= cnt ; i ++ ){
            ll m = ( p[i] - 1 ) * mod;//根据公式可得,我们把mod看做模数,p[i]-1就是b,而qpow( p[i] , sum[i] * b + 1 , m )-1就是a
            ans = ans * ( ( qpow( p[i] , sum[i] * b + 1 , m ) + m - 1 ) / ( p[i] - 1 ) ) % mod ;
        }
        printf( "%I64d\n" , ( ans + mod ) % mod );//防止模后的答案改变
    }
    return 0;
}

其实倒数第六行那个加上m-1的m可以不要

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是用C++实现上述思路的代码: ```cpp #include <iostream> #include <vector> using namespace std; const int mod = 1000000007; // 线性筛法计算欧拉函数的前缀和 void calculatePhi(vector<int>& phi, vector<int>& prime, vector<bool>& isPrime, int k) { phi[1] = 1; for (int i = 2; i <= k; i++) { if (isPrime[i]) { prime.push_back(i); phi[i] = i - 1; } for (int j = 0; j < prime.size() && i * prime[j] <= k; j++) { isPrime[i * prime[j]] = false; if (i % prime[j] == 0) { phi[i * prime[j]] = phi[i] * prime[j]; break; } else { phi[i * prime[j]] = phi[i] * (prime[j] - 1); } } } // 计算前缀和 for (int i = 2; i <= k; i++) { phi[i] = (phi[i] + phi[i - 1]) % mod; } } // 计算⌊n/i⌋的前缀和 vector<int> calculateDivPrefixSum(int n, int k) { vector<int> sumDiv(k + 1, 0); for (int i = 1; i <= k; i++) { sumDiv[i] = (sumDiv[i - 1] + n / i) % mod; } return sumDiv; } int main() { int k; cin >> k; vector<int> phi(k + 1, 0); vector<int> prime; vector<bool> isPrime(k + 1, true); calculatePhi(phi, prime, isPrime, k); vector<int> sumDiv = calculateDivPrefixSum(k, k); int result = 0; for (int i = 1; i <= k; i++) { int factorCount = k / i; int temp = (sumDiv[factorCount] - sumDiv[i - 1] + mod) % mod; result = (result + phi[i] * temp) % mod; } cout << result << endl; return 0; } ``` 你可以将上述代码保存为一个.cpp文件,然后使用C++编译器进行编译和运行。输入k的值,即可得到最终结果。 希望对你有帮助!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值