[武汉加油] 2020.2.15 考试T1

题目描述

给定一个\(n\),求\(\displaystyle \sum_{i=1}^n\sum_{p|i}d(p)\),d(n)表示n的约数个数。

\(n \le 10^{11}\)

方法一:

原式等价于\(\displaystyle \sum_{i=1}^{n}\lfloor \frac{n}{i} \rfloor d(i)\)

线性筛即可

代码就不放了。

方法二:

对于上面的式子\(\displaystyle \sum_{i=1}^{n}\lfloor \frac{n}{i} \rfloor d(i)\)我们可以数论分块,问题就变成了如何快速的求约数个数的前缀和

能1s跑\(10^{10}\)不成问题。但考试的时候没有这部分分。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#define LL long long 
using namespace std;
LL n,ans,tot,M;
const int N=8000010;
int zhi[N],num[N],mu[N];
long long d[N];
bool vis[N];
map<LL,LL>smu,sd;
void HCYY()
{
	mu[1]=d[1]=1;
	for(register int i=2;i<=M;++i)
	{
		if(!vis[i])zhi[++tot]=i,mu[i]=-1,num[i]=1,d[i]=2;
		for(int j=1;j<=tot&&i*zhi[j]<=M;++j)
		{
			vis[i*zhi[j]]=1;
			if(!(i%zhi[j]))
			{
				num[i*zhi[j]]=num[i]+1;
				d[i*zhi[j]]=d[i]/(num[i]+1)*(num[i]+2);
				break;
			}
			num[i*zhi[j]]=1;
			d[i*zhi[j]]=d[i]*d[zhi[j]];
			mu[i*zhi[j]]=-mu[i];
		}
	}
	for(int i=2;i<=M;++i)mu[i]+=mu[i-1],d[i]+=d[i-1];
}
LL suan1(LL n)//mu
{
	if(n<=M)return mu[n];
	if(smu.count(n))return smu[n];
	LL ans=1;
	for(LL l=2,r;l<=n;l=r+1)r=n/(n/l),ans-=(r-l+1)*suan1(n/l);
	return smu[n]=ans;
}
LL suan2(LL n)//d
{
	if(n<=M)return d[n];
	if(sd.count(n))return sd[n];
	LL res=n;
	for(LL l=2,r,las=1,now;l<=n;l=r+1)
	{
		r=n/(n/l);
		now=suan1(r);
		res-=(now-las)*suan2(n/l);
		las=now;
	}
	return sd[n]=res;
}
void solve3()
{
	M=min(N-10,(int)pow(n,0.66));
	HCYY();
	for(LL l=1,r,las=0,now;l<=n;l=r+1)
	{
		r=n/(n/l);
		now=suan2(r);
		ans+=(n/l)*(now-las);
		las=now;
	}
	cout<<ans;
}
int main()
{
	cin>>n;
	solve3();
	return 0;
}

方法三:

原题等价于求 \(xyz \le n\) 的有序三元组个数.

原题等价于求\(\displaystyle \sum_{i=1}^{n}\sum_{p|i}\sum_{d|p}1\),把i拆两次能得到3个数。

假设 \(x \le y \le z\),则 \(x \le n ^{\frac{1}{3}}\)\(\displaystyle y \le \sqrt \frac{n}{x}\)

枚举 x, y,我们可以计算出合法的 z 的数量.

假设合法数为 ans,则 x, y, z 可以任意排列,因此 ans ∗ 6.

但是相等情况会重复计数,我们再计算有两个数相等、三个数全相等的情况数.

两个数相等\(\displaystyle \sum \frac{n}{i^2}\),三个数全相等:\(n ^{\frac{1}{3}}\)

容斥一下就行啦。

代码出奇的短.

#include<iostream>
#include<cmath>
#define LL long long 
using namespace std;
LL n,ans;
int main()
{
	cin>>n;
	LL toi=floor(pow(n,0.34));while(toi*toi*toi>n)--toi;
	for(LL i=1;i<=toi;++i)
		for(LL j=i,toj=sqrt(n/i);j<=toj;++j)ans+=n/(i*j)-j+1;//-j+1是为了使第三个数大于第二个
	ans*=6;
	for(LL i=1;i*i<=n;++i)ans-=(n/i/i)*3;
	ans-=2*toi;
	cout<<ans;
	return 0;
}
posted @ 2020-02-15 15:46  wljss  阅读(187)  评论(0编辑  收藏  举报