2019ICPC西安邀请赛 B. Product(杜教筛,数论分块)详细题解

B. Product
第一次写此类推导证明题目题解,就写详细点。
题意求:
ans=i=1nj=1nk=1nmgcd(i,j)[kgcd(i,j)]mod  p(n,m,p<2e9,p)ans=\prod_{i=1}^{n}\prod_{j=1}^{n}\prod_{k=1}^{n}m^{gcd(i,j)[k|gcd(i,j)]}\mod p (n,m,p<2e9,且p为质数)
因为题目保证了p为质数,所以我们先求指数,然后在求指数的过程中对指数进行费马降幂,最后再对m快速幂即可。
ans=mmimod  (p1)mod  pans=m^{mi\mod (p-1)}\mod p
所以现在的关键是如何求mimi,ansans是累乘所以求mm的幂的时候就变成累加。
mi=i=1nj=1nk=1ngcd(i,j)[kgcd(i,j)]mi=\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{k=1}^{n}gcd(i,j)[k|gcd(i,j)]
这个式子的大意是当gcd(i,j)gcd(i,j)kk的倍数的时候mi+=gcd(i,j)mi+=gcd(i,j),我们改变枚举次序,把k移到外面,并且枚举k的倍数x。
mi=k=1nkxni=1nj=1ngcd(i,j)[gcd(i,j)==x]mi=\sum_{k=1}^{n}\sum_{k|x}^{n}\sum_{i=1}^{n}\sum_{j=1}^{n}gcd(i,j)[gcd(i,j)==x]
mi=k=1nkxnxi=1nj=1n[gcd(i,j)==x]mi=\sum_{k=1}^{n}\sum_{k|x}^{n}x\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(i,j)==x]
mi=k=1nkxnxi=1n/xj=1n/x[gcd(i,j)==1]mi=\sum_{k=1}^{n}\sum_{k|x}^{n}x\sum_{i=1}^{n/x}\sum_{j=1}^{n/x}[gcd(i,j)==1]
式子中的n/xn/x默认是向下取整,对于i=1n/xj=1n/x[gcd(i,j)==1]\sum_{i=1}^{n/x}\sum_{j=1}^{n/x}[gcd(i,j)==1]可以用欧拉函数表示为i=1n/x2ϕ(i)1\sum_{i=1}^{n/x}2\phi(i)-1,为什么呢?
看下面的图片应该可以明白,假设n/x=5n/x=5,那么gcd(i,j)gcd(i,j)就下面这样:
在这里插入图片描述

被重复加上了一次,所以等于i=1n/x2ϕ(i)1\sum_{i=1}^{n/x}2\phi(i)-1,这部分可以用杜教筛求得。于是
mi=k=1nkxnxi=1n/x2ϕ(i)1mi=\sum_{k=1}^{n}\sum_{k|x}^{n}x\sum_{i=1}^{n/x}2\phi(i)-1
下面我们来考虑怎么快速求出
k=1nkxnx\sum_{k=1}^{n}\sum_{k|x}^{n}x
=k=1nk(1+n/k)n/k2()=\sum_{k=1}^{n}\frac{k(1+n/k)n/k}{2}(向下整除)
如果不太清除怎么变形过来的,可以看这里的例3:解释例3
这个算式可以分块在O(n)O(\sqrt n)求出
顺带提一下,比较常用的变形(k=1nkxn=x=1nxd(x)),d(x)x(\sum_{k=1}^{n}\sum_{k|x}^{n}=\sum_{x=1}^{n}x d(x)),d(x)是x的约数个数
有了这个变形,就可以在预处理的时候对d(x)d(x)进行预处理,对于没预处理到的部分我们就用O(n)O(\sqrt n)的分块算法计算,然后记忆化减少计算量。

这一部分可以用整除分块算出来。最终的式子就是这个:
mi=k=1nk(1+n/k)n/k2i=1n/x2ϕ(i)k=1nk(1+n/k)n/k2mi=\sum_{k=1}^{n}\frac{k(1+n/k)n/k}{2}\sum_{i=1}^{n/x}2\phi(i) -\sum_{k=1}^{n}\frac{k(1+n/k)n/k}{2}

代码如下:

#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));
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章