poj 1845(快速幂+二分计算等比数列和+大数因子分解+因子和计算+模除溢出)

Sumdiv
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 12755 Accepted: 3100

Description

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.

Sample Input

2 3

Sample Output

15

Hint

2^3 = 8.  
The natural divisors of 8 are: 1,2,4,8. Their sum is 15.  
15 modulo 9901 is 15 (that should be output).  

Source


题目非常好,考察的技巧很多。
1、同模余定理:
%运算对于加减乘法是随意的。可以随便打开括号。因此为了避免溢出,要经常做%运算。
2、等比数列求和:
1 + 2 + 2^2 + 2^3 ... + 2^n 计算的时候,不能用等比求和公式,因为 除法不满足同模余定理,在求余数的时候会有错误。因此使用一个小trick,用二分来解决(非常巧妙):
    当n为偶数时: 1+2+...+2^n  =  (1 + 2 + ... + 2 ^ (n / 2)) * (1 + 2 ^ (n / 2 + 1)) - 2 ^ (n + 1)
    当n为奇数时: 1+2+...+2^n  =  (1 + 2 + ... + 2 ^ (n / 2)) * (1 + 2 ^ (n / 2 + 1))
3、大数因子分解:
    任意一个自然数都可以分解为素数次幂的乘积,而且 分解方式唯一。也就是说,分解方式与原自然数 是 一一对应的!
     比如300 可以分解为 2 * 2 * 3 * 5 * 5
     用数组表示就是:
        素数:         2   3   5
        对应个数: 2   1   2
     分解算法千万不要去打素数表,因为当输入非常大(比如这题的5000万),打素数表会直接超时,即使用筛法)
     直接去分解,就会自然形成素数序列(有点类似筛素数了,比如2被分解干净后,2的倍数就不会再被分解出来)
    cin >> a >> b;
    ll tmp = a;
    int num = 0;
    for (i = 2; tmp != 1 && i * i <= a; i++) { //这里是tmp != 1,终止条件是i*i <= a !!!因为所有因子中,最坏情况只能是有一个素因子大于sqrt(a),因此最后特殊考虑一下即可。
        while (tmp % i == 0) {
            p[num] = i;    
            n[num] ++;
            tmp /= i;
        }
        if (p[num] != 0) num++;
    }
    if (tmp != 1) { //这里是tmp != 1!!!
        p[num] = tmp;
        n[num] = 1;
        num++;
    }


4、大数因子和:
     分解出上面的数组后,其实因子和就很容易求出来了:
     其实就是(1 + 2 + 2^2) * (1 + 3) * (1 + 5 + 5^2)    想想为什么? 其实 原数的因子就是 在(1, 2, 4), (1, 3), (1, 5, 25)  这三个集合中各挑一个,然后相乘,就可以形成一个因子。
因此一共会有3 * 2 * 3种挑选方法,亦即3*2*3个因子。
     我刚开始思考的时候,是想用dp的方法计算这些因子中,各个余数的数量,然后求和。这么做显然会超时。 其实就是原来的 括号内先求和 ,再相乘。就等同于把括号打开后计算乘法后再相加。
5、快速幂算法:
    这个我不想多说了,这次居然又写错一次,还另外溢出WA了一次:
    
ll pow(ll a, ll b) {
    ll result = 1;
    while (b > 0) {
        if (b % 2 == 1) {
            result = result * a % mod;
        }
        b /= 2;
        a = a * a % mod; //这里要写对!!而且要加上mod
    }
    return result;
}


提交记录:
1、WA。因为没有考虑最后大于sqrt(n)的那 一个素因子。
2、WA。素因子分解的时候,循环条件应该是tmp != 1,而不是tmp != 0。因此这里只有可以整除的时候才会调用/, 所以不会除到0.
3、WA。每一次计算出来的result,都要去%mod,否则 可能溢出
4、WA。快速幂中德a = a * a 一定要改为a = a * a % mod ,否则 会溢出
5、WA。在计算cal中,减去的时pow(a, b+1),这里粗心了,写成了pow(a, b).
6、WA。为了保险, 最后结果如果为负数的时候,要先加上一个mod,再模除一下。
7、AC

代码:
/*Source Code

Problem: 1845		User: 775700879
Memory: 692K		Time: 32MS
Language: G++		Result: Accepted
Source Code*/
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
        
        
#include 
        
        
          #include 
         
           #include 
          
            #include 
           
             #include 
            
              #include 
             
               #include 
               #define MAXN 50000010 #define mod 9901 #define oo 0x3f3f3f3f #define UPPER 2147483647 using namespace std; typedef long long int ll; ll p[10000] = {0}, n[10000] = {0}; ll pow(ll a, ll b) { ll result = 1; while (b > 0) { if (b % 2 == 1) { result = result * a % mod; } b /= 2; a = a * a % mod; //这里要写对!!而且要加上mod } return result; } int cal(ll a, ll b) { if (b == 1) return (1+a) % mod; if (b == 0) return 1; if (b % 2 == 0) { return cal(a, b/2) % mod * (1 + pow(a, b/2+1)) - pow(a, b+1) % mod; //这里是b+1!!! } else { return cal(a, b / 2) % mod * (1 + pow(a, b / 2 + 1)); } } int main() { ll i, j; ll a, b; cin >> a >> b; ll tmp = a; int num = 0; for (i = 2; tmp != 1 && i * i <= a; i++) { //这里是tmp != 1 while (tmp % i == 0) { p[num] = i; n[num] ++; tmp /= i; } if (p[num] != 0) num++; } if (tmp != 1) { //这里是tmp != 1!!! p[num] = tmp; n[num] = 1; num++; } int result = 1; for (i = 0; i < num; i++) { n[i] *= b; result *= cal(p[i], n[i]) % mod; result %= mod; //这里要加上!!! } result %= mod; if (result < 0) result += mod;//加上一个mod,防止结果出来负数 cout << result % mod << endl; return 0; }  
              
             
            
           
          
        
       
       
      
      
     
     
    
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值