B. Product
第一次写此类推导证明题目题解,就写详细点。
题意求:
因为题目保证了p为质数,所以我们先求指数,然后在求指数的过程中对指数进行费马降幂,最后再对m快速幂即可。
所以现在的关键是如何求,是累乘所以求的幂的时候就变成累加。
这个式子的大意是当是的倍数的时候,我们改变枚举次序,把k移到外面,并且枚举k的倍数x。
式子中的默认是向下取整,对于可以用欧拉函数表示为,为什么呢?
看下面的图片应该可以明白,假设,那么就下面这样:
被重复加上了一次,所以等于,这部分可以用杜教筛求得。于是
下面我们来考虑怎么快速求出
如果不太清除怎么变形过来的,可以看这里的例3:解释例3
这个算式可以分块在求出
顺带提一下,比较常用的变形
有了这个变形,就可以在预处理的时候对进行预处理,对于没预处理到的部分我们就用的分块算法计算,然后记忆化减少计算量。
这一部分可以用整除分块算出来。最终的式子就是这个:
代码如下:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int N=5e6;
int n,m,mod,tot=0;
bool vis[N];
int p[N/10],phi[N],sum[N];// phi(x),x*d(x)的前缀和
unordered_map<int, int>M_phi,M_sum;
void add(int &x,int y){//手写模运算防止卡常数
if(y<0)x+=y;
else x=x-mod+y;
if(x<0)x+=mod;
}
void init(){
phi[1]=sum[1]=1;
for(int i=2;i<N;i++){
if(!vis[i]){
sum[i]=2;
phi[i]=i-1;
p[tot++]=i;
}
for(int j=0;j<tot&&i*p[j]<N;j++) {
int v=i*p[j];
vis[v]=1;
if(i%p[j]==0){
phi[v]=phi[i]*p[j];
int d=1,x=i;
while(x%p[j]==0)
x/=p[j],d++;
sum[v]=sum[i]/d*(d+1);
break;
}
phi[v]=phi[i]*(p[j]-1);
sum[v]=sum[i]*2;
}
}
for(int i=1;i<N;i++){
add(phi[i],phi[i-1]);
sum[i]=1ll*sum[i]*i%mod;
add(sum[i],sum[i-1]);
}// phi(x),x*d(x)的前缀和
}
int S_phi(int x){
if(x<N)return phi[x];
if(M_phi[x])return M_phi[x];
int ret=0;
for(ll l=2,r;l<=x;l=r+1)
r=x/(x/l),add(ret,S_phi(x/l)*(r-l+1)%mod);
return M_phi[x]=((ll)x*(x+1)/2-ret+mod)%mod;
}
int S_sum(int x){
if(x<N)return sum[x];
if(M_sum[x])return M_sum[x];
int res=0;
for(int l=1,r;l<=x;l=r+1){
r=x/(x/l);
add(res,(1ll*(l+r)*(r-l+1)/2 %mod *1ll*(1+x/l)*(x/l)/2% mod)% mod);
}
return M_sum[x]=res;
}
int fp(int x,int y){
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
y/=2;
}
return ret;
}
int main(){
scanf("%d%d%d",&n,&m,&mod);
mod--; //费马降幂
init();
int ans=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
int temp=S_sum(r);
add(temp,-S_sum(l-1));
add(ans,1ll*S_phi(n/l)*temp%mod);
}
add(ans,ans),add(ans,-S_sum(n));
mod++;
printf("%d\n",fp(m,ans));
}