bzoj3944 sum 杜教篩

題目鏈接:傳送門

前置技能:

莫比烏斯反演 & 狄利克雷卷積
杜教篩基礎

解題思路:

點亮前置技能就珂以發現這是裸題……
首先杜教篩要卷積一個函數。
先考慮S(n)=Σi=1nφ(i)S(n)=\Large\Sigma\large_{i=1}^{n}φ(i)的情況:
發現有一條式子叫做φI=idφ*I=id
所以讓兩邊卷積一個II,得到(IS)(n)=Σi=1nid(i)=n(n+1)2(I*S)(n)=\Large\Sigma\large_{i=1}^{n}id(i)=\frac{n*(n+1)}{2}
按照杜教篩的套路亂搞一波(此處可直接套杜教篩的結論),得到:
S(n)=Σi=1n(Iφ)(i)Σi=2nI(i)S(ni)S(n)=\Large\Sigma\large_{i=1}^n(I*φ)(i)-\Large\Sigma\large_{i=2}^nI(i)S(\frac{n}{i})
=n(n+1)2Σi=2nS(ni)=\large\frac{n*(n+1)}{2}-\Large\Sigma\large_{i=2}^nS(\frac{n}{i})
然後整除分塊+記憶化亂搞即可。

對於S(n)=Σi=1nμ(i)S(n)=\Large\Sigma\large_{i=1}^{n}μ(i)的情況,也類似地套模板即可。

奇技淫巧

發現遞歸的時候nn很大,記憶化不太好搞,這裏需要map奇技淫巧來解決。
遞歸時需要記憶化的值只有n2,n3,....\lfloor\frac{n}{2}\rfloor,\lfloor\frac{n}{3}\rfloor,....(這個先不證明)
因爲當遞歸傳遞的值小於n23n^{\frac{2}{3}}(一般設爲1e61e6左右)時,是直接線性篩出來的,所以珂以考慮對於xx,把需要記憶化的valval存在nx\lfloor\frac{n}{x}\rfloor的位置。
因爲x>106x>10^6(否則調用線性篩的結果直接返回),nn101010^{10}左右,所以nx\lfloor\frac{n}{x}\rfloor10410^4以下。
這樣就珂以不用開一個101010^{10}的數組或是mapmap來記憶化了。
這裏放一張圖:
在這裏插入圖片描述

但是這樣會不會衝突呢?比如n23\Large\lfloor\frac{\lfloor\frac{n}{2}\rfloor}{3}\rfloor會不會與n6\large\lfloor\frac{n}{6}\rfloor不相等,導致需要記憶化的位置不只有nx\lfloor\frac{n}{x}\rfloor呢?
這裏需要引進一條玄學定理,叫做nab=nab\Large\lfloor\frac{\lfloor\frac{n}{a}\rfloor}{b}\rfloor=\lfloor\frac{n}{ab}\rfloor
這裏就不證明了,珂以看OIWiki-莫比烏斯反演中的證明qwq
根據引理珂以看出不存在nx(xn)\lfloor\frac{n}{x}\rfloor(x\le n)之外需要記憶化的位置。
所以如上亂搞即可qwq

另外,這題卡常數,要把兩個前綴和在一個函數裏面求出來。

毒瘤代碼

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define mod 1000000000
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=2000005;
ll tot,mu[Size],phi[Size],prime[Size];
ll summu[Size],sumphi[Size];
bool vis[Size];
void getp(int maxn) {
	phi[1]=mu[1]=1;
	phi[0]=0;
	for(re i=2; i<=maxn; i++) {
		if(!vis[i]) {
			prime[++tot]=i;
			mu[i]=-1;
			phi[i]=i-1;
		}
		for(re j=1; j<=tot; j++) {
			int now=i*prime[j];
			if(now>maxn)	break;
			vis[now]=true;
			if(i%prime[j]==0) {
				mu[now]=0;
				phi[now]=phi[i]*prime[j];
				break;
			}
			phi[now]=phi[i]*phi[prime[j]];
			mu[now]=-mu[i];
		}
	}
	for(re i=1; i<=maxn; i++) {
		summu[i]=summu[i-1]+mu[i];
		sumphi[i]=sumphi[i-1]+phi[i];
	}
}
ll n,m1[Size],m2[Size];
bool vis1[Size],vis2[Size];
pair<ll,ll> getans(ll x) {
	if(x<=2000000)	return make_pair(sumphi[x],summu[x]);
	int t=n/x;
	if(vis1[t])	return make_pair(m1[t],m2[t]);
	ll sum1=0,sum2=0,lst;
	for(ll i=2; i<=x; i=lst+1) {
		lst=x/(x/i);
		pair<ll,ll> tmp=getans(x/i);
		sum1+=(lst-i+1)*tmp.first;
		sum2+=(lst-i+1)*tmp.second;
	}
	vis1[t]=true;
	m1[t]=(x*(x+1ll)>>1ll)-sum1;
	m2[t]=1ll-sum2;
	return make_pair(m1[t],m2[t]);
}
int main() {
	getp(2000000);
	int T=read();
	while(T--) {
		n=read();
		memset(vis1,0,sizeof(vis1));
		memset(vis2,0,sizeof(vis2));
		pair<ll,ll> ans=getans(n);
		printf("%lld %lld\n",ans.first,ans.second);
	}
	return 0;
}
/*
1
2147483647
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章