P2398 GCD SUM

Description

i=1nj=1ngcd(i,j)

Solution

这种gcd计数的题一般思想是枚举gcd

对于这道题,有一下几种做法,循序渐进

暴力:O(n2logn)

就是暴力枚举所有数求gcd,期望得分不清楚,大概20pts

可以优化gcd函数,记忆化一下。

画个矩阵发现可以只求下三角,即只求i=1nj=1j<igcd(i,j),将答案2后再单独用等差数列求和公式处理对角线上的情况(i==j),一顿操作猛如虎,但因为会MLE仍然无法通过70pts

离正解只差一步的暴力:O(nn)

枚举所有数i,设组成数对的另一个数为j,仍是只考虑i<j的情况。设gcd(i,j)=k,则gcd(ik,jk)=1,符合这样的数jk的个数就应该是ϕ(ik),所以O(n)枚举i的所有因数为gcdans+=gcdϕ(igcd)。总复杂度O(nn)

70pts肯定是稳过的,至此离正解只差一步。

正解O(nlnn)

上面枚举因子O(n)显然是可以优化的,可以先枚举gcd,再枚举另一个因子得出i=gcdx,和上面方法是等效的,调和级数O(lnn)总复杂度O(nlnn)

Code

int n,prime[maxn],pcnt,is_not_prime[maxn];
int phi[maxn];
ll ans;
void Get_Phi(int x)
{
	is_not_prime[1]=1;
	for(re int i=2;i<=x;++i)
	{
		if(!is_not_prime[i]) prime[++pcnt]=i,phi[i]=i-1;
		for(re int j=1;j<=pcnt;++j)
		{
			if(i*prime[j]>x) break;
			is_not_prime[i*prime[j]]=1;
			if(!(i%prime[j]))
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}
}
int vis[maxn];
int main()
{
    n=read();
    Get_Phi(n);
    for(re int i=1;i<=n;++i)//枚举gcd
		for(re int j=2;j*i<=n;++j)//枚举数对中较大的数/gcd的结果,从2枚举是为了不考虑对角线 
		{
			ans+=(ll)phi[j]*i;
		}
	ans*=2;//上三角下三角 
	ans+=(ll)(1+n)*n/2;//统计对角线 
	printf("%lld\n",ans);
	return 0;
}

Updata

当我在luogu刷这道题的k倍经验时,遇到了一道多组数据且T<=200000的题,于是成功TLE
其实只需要对上面算法进行小的改动就行

    ans+=(ll)phi[j]*i;

注意到这一行ans更新的其实是x=1x<ijgcd(x,ij),相当于对ij的答案更新
那么可以这样写

    f[i*j]+=(ll)phi[j]*i;

每个答案就是i=1nf[i]
所以对于数据个数很多的情况,预处理到最大范围,求前缀和,O(1)回答即可。

posted @   __Liuz  阅读(336)  评论(4编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示