洛谷 P1829 [国家集训队]Crash的数字表格 / JZPTAB 莫比乌斯反演

P1829

我又来推式子了(找虐了),假设n<=m

                                  ans=\sum_{i=1}^{n}\sum_{j=1}^{m}\frac{ij}{gcd(i,j)}

这个gcd作分母看起来有点麻烦,我们去掉它:

                          ans=\sum_{d=1}^{n}\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)=d]\frac{ij}{d}

与其枚举两个数看他们的gcd是否等于d,不如直接枚举d的倍数:

                          ans=\sum_{d=1}^{n}\sum_{i=1}^{\frac{n}{d}}\sum_{j=1}^{\frac{m}{d}}[gcd(i,j)=1]ijd

把d提前:

                          ans=\sum_{d=1}^{n}d\sum_{i=1}^{\frac{n}{d}}\sum_{j=1}^{\frac{m}{d}}[gcd(i,j)=1]ij

                               [gcd(i,j)=1]\Leftrightarrow \sum_{x|gcd(i,j)}\mu (x)

代换:

                          ans=\sum_{d=1}^{n}d\sum_{i=1}^{\frac{n}{d}}\sum_{j=1}^{\frac{m}{d}}\sum_{x|gcd(i,j)}\mu (x)ij        

再次切换枚举,从枚举因数到枚举倍数:

                          ans=\sum_{d=1}^{n}d\sum_{x=1}^{\frac{n}{d}}\mu (x)\sum_{i=1}^{\frac{n}{dx}}\sum_{j=1}^{\frac{m}{dx}}ijx^2

化简:

                         ans=\sum_{d=1}^{n}d\sum_{x=1}^{\frac{n}{d}}\mu (x)x^2(\sum_{i=1}^{\frac{n}{dx}}i)(\sum_{j=1}^{\frac{m}{dx}}j)

接下来我们打表u(x)x^2和等差数列求和,只用枚举d,然后用整除分块求后面的式子就行了。//后面还可以优化哦

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e7+5,mod=20101009;
int vis[maxn],pri[maxn],mu[maxn],cnt;
int g[maxn],sum[maxn];
void get_mu(int n)
{
	mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		pri[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&pri[j]*i<=n;j++)
		{
			vis[i*pri[j]]=1;
			if(i%pri[j]==0)break;
			else mu[i*pri[j]]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++)
	{
		sum[i]=(sum[i-1]+i)%mod;
		g[i]=(g[i-1]+1ll*mu[i]*i*i%mod)%mod;	
	}
}
int main()
{
	int n,m;
	ll ans=0;
	get_mu(1e7);
	scanf("%d%d",&n,&m);
	if(n>m)swap(n,m);
	for(int i=1;i<=n;i++)
	{
		int nn=n/i,mm=m/i;
		for(int l=1,r;l<=nn;l=r+1)
		{
			r=min(nn/(nn/l),mm/(mm/l));
			ans+=1ll*i*(g[r]-g[l-1]+mod)%mod*sum[nn/l]%mod*sum[mm/r]%mod;
		}
	}
	printf("%lld\n",ans%mod);
}

其实我们可以把第一层for循环枚举也来整除分块,这样更快哦。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e7+5,mod=20101009;
int vis[maxn],pri[maxn],mu[maxn],cnt;
int g[maxn],sum[maxn];
void get_mu(int n)
{
	mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		pri[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&pri[j]*i<=n;j++)
		{
			vis[i*pri[j]]=1;
			if(i%pri[j]==0)break;
			else mu[i*pri[j]]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++)
	{
		sum[i]=(sum[i-1]+i)%mod;
		g[i]=(g[i-1]+1ll*mu[i]*i*i%mod)%mod;	
	}
}
ll work(int n,int m)
{
	ll ans=0;
	for(int l=1,r;l<=n;l=r+1)
	{
		r=min(n/(n/l),m/(m/l));
		ans+=1ll*(g[r]-g[l-1]+mod)%mod*sum[n/l]%mod*sum[m/r]%mod;
	}	
	return ans;
}
ll cal(int n)
{
	return 1ll*n*(n+1)/2%mod;
}
int main()
{
	int n,m;
	ll ans=0;
	get_mu(1e7);
	scanf("%d%d",&n,&m);
	if(n>m)swap(n,m);
	for(int l=1,r;l<=n;l=r+1)
	{
		r=min(n/(n/l),m/(m/l));
		ans+=(cal(r)-cal(l-1)+mod)*work(n/l,m/l)%mod;
	}
	printf("%lld\n",ans%mod);
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值