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