【POJ1845】Sumdiv——杨子曰题目

【POJ1845】Sumdiv——杨子曰题目

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).


一句话题意: A B A^B AB的所有约数之和mod9901( 1 ≤ A , B ≤ 5 ∗ 1 0 7 1\leq A,B\leq5*10^7 1A,B5107)


首先,看到题面直觉告诉就告诉我一定要对A分解质因数,那我们就来分解一下:
A = p 1 c 1 ∗ p 2 c 2 ∗ ⋯ ∗ p n c n A={p_1}^{c_1}*{p_2}^{c_2}*\cdots*{p_n}^{c_n} A=p1c1p2c2pncn

这样一来:
A B = p 1 B ∗ c 1 ∗ p 2 B ∗ c 2 ∗ ⋯ ∗ p n B ∗ c n A^B={p_1}^{B*c_1}*{p_2}^{B*c_2}*\cdots*{p_n}^{B*c_n} AB=p1Bc1p2Bc2pnBcn

现在我们要求出所有的约数之和,约数怎么来呢?
我可以随便抓几个(0到 B ∗ c 1 B*c_1 Bc1之间) p 1 p_1 p1,随便抓几个(0到 B ∗ c 2 B*c_2 Bc2之间) p 2 p_2 p2 ⋯ \cdots ,随便抓几个(0到 B ∗ c n B*c_n Bcn之间) p n p_n pn,然后把它们全部乘起来就可以得到一个 A B A^B AB的约数

我们就可以得到它的和是:
( 1 + p 1 + p 1 2 + ⋯ + p 1 B ∗ c 1 ) ∗ ( 1 + p 2 + p 2 2 + ⋯ + p 2 B ∗ c 2 ) ∗ ⋯ ∗ ( 1 + p n + p n 2 + ⋯ + p n B ∗ c n ) (1+p_1+{p_1}^2+\cdots+{p_1}^{B*c_1})*(1+p_2+{p_2}^2+\cdots+{p_2}^{B*c_2})*\cdots*(1+p_n+{p_n}^2+\cdots+{p_n}^{B*c_n}) (1+p1+p12++p1Bc1)(1+p2+p22++p2Bc2)(1+pn+pn2++pnBcn)

简单解释一下上面那个式子是怎么得到的:现在我们来脑补一下把上面的括号全部打开,那么每一项都是一个形如这样的式子:
p 1 x 1 ∗ p 2 x 2 ∗ ⋯ ∗ p n x n {p_1}^{x_1}*{p_2}^{x_2}*\cdots*{p_n}^{x_n} p1x1p2x2pnxn

有没有发现就是对应了每一个 A B A^B AB的约数

也就是说如果我们可以快速地求出 1 + p + p 2 + ⋯ + p c 1+p+{p}^2+\cdots+{p}^c 1+p+p2++pc问题就可以轻松解决了(也就是等比数列求和)

接下来我们讲两个方法算出上面那个东西(一个需要逆元,一个不需要逆元,戳我学习逆元)

我们令 s u m ( p , c ) = 1 + p + p 2 + ⋯ + p c sum(p,c)=1+p+p^2+\cdots+p^c sum(p,c)=1+p+p2++pc就是我们要求的东西


先来讲一讲不需要逆元的:

我们使用分治

如果c是奇数,那么:
s u m ( p , c ) = ( 1 + p + p 2 + ⋯ + p c − 1 2 ) + ( p c + 1 2 + p c + 3 2 + ⋯ + p c ) sum(p,c)=(1+p+p^2+\cdots+p^\frac{c-1}{2})+(p^\frac{c+1}{2}+p^\frac{c+3}{2}+\cdots+p^c) sum(p,c)=(1+p+p2++p2c1)+(p2c+1+p2c+3++pc)

s u m ( p , c ) = ( 1 + p + p 2 + ⋯ + p c − 1 2 ) + p c + 1 2 ∗ ( 1 + p + p 2 + ⋯ + p c − 1 2 ) sum(p,c)=(1+p+p^2+\cdots+p^\frac{c-1}{2})+p^\frac{c+1}{2}*(1+p+p^2+\cdots+p^{\frac{c-1}{2}}) sum(p,c)=(1+p+p2++p2c1)+p2c+1(1+p+p2++p2c1)
然后你有没有惊奇的发现规模所缩小了一半:
s u m ( p , c ) = ( 1 + p c + 1 2 ) ∗ s u m ( p , c − 1 2 ) sum(p,c)=(1+p^\frac{c+1}{2})*sum(p,\frac{c-1}{2}) sum(p,c)=(1+p2c+1)sum(p,2c1)

美滋滋,我们再来看看如果c是偶数怎么办?好办呀,拎出一个 p c p^c pc就好了呀!然后c-1就是奇数了!,我们把c-1带回上面的公式得到了:
s u m ( p , c ) = ( 1 + p c 2 ) ∗ s u m ( p , c 2 − 1 ) + p c sum(p,c)=(1+p^\frac{c}{2})*sum(p,\frac{c}{2}-1)+p^c sum(p,c)=(1+p2c)sum(p,2c1)+pc

于是偶数的规模也缩小了一半,我们成功地在O(log c)的时间里求出了等比数列的和(当然要用快速幂)

#include
#include
#define ll long long
#define mod 9901
using namespace std;

ll a,b,ans=1;
int p[25],c[25],cnt=0;

void get_pr(int n){
	for (int i=2;i*i<=n;i++){
		if (n%i==0){
			p[++cnt]=i;
			while(n%i==0) c[cnt]++,n/=i;
		}
	}
	if (n>1){
		p[++cnt]=n;
		c[cnt]=1;
	}
}

ll pow(ll a,ll b,ll p){
	ll ans=1;
	while(b){
		if (b%2==1) ans=ans*a%p;
		a=a*a%p;
		b/=2;
	}
	return ans;
}

ll sum(ll p,ll s){
    if (s==0) return 1;
    if (s%2==1) return (1+pow(p,(s+1)/2,mod))*sum(p,(s-1)/2)%mod;
    else return ((1+pow(p,s/2,mod))*sum(p,s/2-1)%mod+pow(p,s,mod))%mod;
}

int main(){
	scanf("%lld%lld",&a,&b);
    get_pr(a);
        for (int i=1;i<=cnt;i++){
            ans=(ans*sum(p[i],1ll*c[i]*b))%mod;
        }
    cout<<ans;
    return 0;
}

我们再来看一下第二种要用逆元的方法(戳我学习逆元)
我们这样来操作一下等比数列:

首先原等比数列长这样:
s u m ( p , c ) = 1 + p + p 2 + ⋯ + p c   ⋯ ① sum(p,c)=1+p+p^2+\cdots+p^c\ \cdots① sum(p,c)=1+p+p2++pc 
我们给它乘上一个p得到了:
s u m ( p , c ) ∗ p = p + p 2 + p 3 + ⋯ + p c + 1   ⋯ ② sum(p,c)*p=p+p^2+p^3+\cdots+p^{c+1}\ \cdots② sum(p,c)p=p+p2+p3++pc+1 

然后我们把②式①式错位相减,你会发现等式右边中间的部分都被抵消没了:
s u m ( p , c ) ∗ ( p − 1 ) = p c − 1 − 1 sum(p,c)*(p-1)=p^{c-1}-1 sum(p,c)(p1)=pc11
我们得到了:
s u m ( p , c ) = p c − 1 − 1 p − 1 sum(p,c)=\frac {p^{c-1}-1}{p-1} sum(p,c)=p1pc11

模数9901是一个质数,So使用快速幂和逆元我们可以轻松算出这个式子!

BUT,还有一个问题我们没有解决,就是如果(p-1)是9901的倍数那么就没有逆元了,肿么半捏?

现在考虑(p-1)是9901的倍数,我们有:
p   m o d   9901 = 1 p\ mod\ 9901=1 p mod 9901=1

那么 1 + p + p 2 + ⋯ + p c 1+p+p^2+\cdots+p^c 1+p+p2++pc中的每一项模9901得到的答案都是1,So,这个时候 s u m ( p , c ) = c + 1 sum(p,c)=c+1 sum(p,c)=c+1

#include
#include
#define ll long long
#define mod 9901
using namespace std;

ll a,b,ans=1;
int p[25],c[25],cnt=0;

void get_pr(int n){
	for (int i=2;i*i<=n;i++){
		if (n%i==0){
			p[++cnt]=i;
			while(n%i==0) c[cnt]++,n/=i;
		}
	}
	if (n>1){
		p[++cnt]=n;
		c[cnt]=1;
	}
}

ll pow(ll a,ll b,ll p){
	ll ans=1;
	while(b){
		if (b%2==1) ans=ans*a%p;
		a=a*a%p;
		b/=2;
	}
	return ans;
}

int main(){
	scanf("%lld%lld",&a,&b);
	get_pr(a);
	for (int i=1;i<=cnt;i++){
		if ((p[i]-1)%mod==0){
			ans=1ll*(b*c[i]+1)%mod*ans%mod;
		}
		else{
			ll x=pow(p[i],1ll*b*c[i]+1,mod);
			x=(x-1+mod)%mod;
			ll y=pow(p[i]-1,mod-2,mod);
			ans=1ll*x*y%mod*ans%mod;
		}
	}
	cout<<ans;
	return 0;
}

OK,完事

参考:

《算法竞赛进阶指南》 李煜东 著

于HG机房

你可能感兴趣的:(恶心的题目,崩溃的数学)