bzoj 3994: [SDOI2015]约数个数和(反演)

26 篇文章 0 订阅

3994: [SDOI2015]约数个数和

Time Limit: 20 Sec   Memory Limit: 128 MB
Submit: 562   Solved: 375
[ Submit][ Status][ Discuss]

Description

 设d(x)为x的约数个数,给定N、M,求  

Input

输入文件包含多组测试数据。

第一行,一个整数T,表示测试数据的组数。
接下来的T行,每行两个整数N、M。

Output

 T行,每行一个整数,表示你所求的答案。

Sample Input

2
7 4
5 6

Sample Output

110
121

HINT

 1<=N, M<=50000


1<=T<=50000

Source

[ Submit][ Status][ Discuss]

题解:一道不错的数论题,很考验人的智商,很久之前就看过了,当时什么都看不懂,现在总算自己推出来了。

这道题用到了一个很神的结论 


证明如下:我们知道了这个自变量的乘积拆分形式,那么如果我们枚举n的所有约数i,枚举m的所有约数j,那么i*j一定是n*m的一个约数,于是所有的i*j就包括了n*m的所有约数,那么我们只要知道有多少不同的i*j就可以了。为了知道不同的i*j,我们可以考虑如何去掉重复的i*j。这里我们考虑把ij分为互质和不互质两种情况,设gcd(i,j)=k,于是i=p*kj=q*k。那么i*j就可以重新组合成两个互质数的积p*k*kq,而这两个数也分别是nm的约数,即合法的ij。这一点说明了一个问题:如果枚举到的ij不互质,那么i*j这个数一定可以由另外一对互质的i’j’表示,也就是这一对ij是重复的;而当ij互质的时候,p*k*kq显然就是ij,所以它是唯一的。于是我们总结一下上面的结论:所有ij的积包括了n*m的所有约数,当ij不互质时,i*j可以由另一对互质的i’j’表示。所以我们得到所有互质的ij的乘积就不重复地包含了n*m的所有约数,写成公式就是:d(n*m)=sigma(i|n)sigma(j|m)[(i,j)==1]。得证。

那么题目中的式子就可以进一步化简

假设N<M,

sigma(n=1..N)sigma(m=1..M) d(nm)

=sigma(n=1..N)sigma(m=1..M)sigma(i|n)sigma(j|m)[(i,j)==1]

我们改成先枚举i,j 可知i最大到N, 而j 最大到M  ,i表示的是n 的约数,因sigma(n=1..N)这中间的数都要计算约数i,所有把就可以变成 sigma(i=1..N) [N/i] (这里的中括号表示下取整),j也是同理。

于是式子就变成了  sigma(i=1..N)sigma(j=1..M) [N/i][M/j] [gcd(i,j)=1]

根据[n==1]=sigma(d|n) mu[d] 就可以得到

sigma(i=1..N)sigma(j=1..M) [N/i][M/j] sigma(d|gcd(i,j)) mu[d]

sigma(i=1..N)sigma(j=1..M) [N/i][M/j] sigma(d=1..min(i,j))[d|i][d|j] mu[d]

把d 提到最前面,得到

sigma(d=1..N) sigma(i=1..[N/d])sigma(j=1..[M/d]) [N/di] [M/dj](这里是下取整) mu[d]

根据公式 [N/ab](下取整)=[[N/a]/b],进一步化简

设f(x)=sigma(i=1..x)[x/i] (下取整) 这个式子可以在o(sqrt(n))的时间内求解,一开始可以直接预处理出所有数的答案。

原式=sigma(d=1..N) f(N/d)*f(M/d) *mu[d]  因为N/d的取值都是成块分布的,所有可以预处理出mu的前缀和,

整个式子可以在o(sqrt(n))的时间内求解。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 50003
#define ll long long 
using namespace std;
int n,m,t;
long long ans;
int prime[N],mu[N],pd[N],smu[N];
ll f[N];
void calc()
{
	mu[1]=1; smu[1]=1;
	for (int i=2;i<=N;i++)
	 {
	 	if (!pd[i]) {
	 		prime[++prime[0]]=i; mu[i]=-1;
	 	}
	 	for (int j=1;j<=prime[0];j++)
	 	 {
	 	 	if (i*prime[j]>N) break;
	 	    pd[i*prime[j]]=1;
	 	    if (i%prime[j]==0)
	 	     {
	 	     	mu[i*prime[j]]=0;
	 	     	break;
	 	     }
	 	    else
	 	     mu[i*prime[j]]=-mu[i];
	 	 }
	 	smu[i]=smu[i-1]+mu[i];
	 }
}
ll F(int x)
{
	ll sum=0; int j=1;
	for (int i=1;i<=x;i=j+1)
	 {
	 	j=x/(x/i);
	 	sum+=(ll)(j-i+1)*(ll)(x/i);
	 }
	return sum;
}
int main()
{
	scanf("%d",&t);
	calc();
	for (int i=1;i<=N;i++)
	 f[i]=(ll)F(i);
	for (int T=1;T<=t;T++)
	{
	    scanf("%d%d",&n,&m);
	    if (n>m) swap(n,m);
	    ll ans=0;
	    int j=0;
	    for (int i=1;i<=n;i=j+1)
	     {
	 	   ll a=f[n/i]; ll b=f[m/i];
	 	   j=min(n/(n/i),m/(m/i));
	 	   ans+=(ll)a*b*(smu[j]-smu[i-1]);
	     }
	    printf("%I64d\n",ans);
    }
	return ans;
} 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值