=== ===
这里放传送门
=== ===
题解
SDOI怎么净出这样goushi的数论。。让不让人活T_T
题目要求的是
∑i=1N∑j=1Md(i×j)
。啥?约数个数是可以筛的?但是这个题一看数据范围显然不可行啊QwQ。。但是这个题目关键的一个特点就是它要求
d
的那个东西已经给表示成了
那么我们只需要知道有多少本质不同的
pi×qj
就可以了。显然如果
pi
和
qj
不互质,那么这一组乘积一定能被另外两个互质的数的乘积代替,并且这两个数肯定也分别是
i
和
于是我们把这个式子代入上面那个初始公式得到:
然而只有这个式子还是不够,我们需要进一步化简。首先把式子展开然后
可以发现其中
∑i=1N[p|i]
这一句就是在问
1..N
的数字中有多少数字是
p
的倍数,那么这整个式子和
发现后面有一个
[(i,j)==1]
的东西,我们可以考虑再把它搞一下转化成一个带
μ
的式子。以下设
N
为
首先利用反演公式
注意到
i
和
接下来我们要利用下取整函数的一个性质: ⌊xa×b⌋=⌊⌊xa⌋b⌋ 。于是对于 ⌊Ni×d⌋ 这个东西,我们可以把它化成 ⌊⌊Nd⌋i⌋ 。这个时候再观察 ∑i=1⌊Nd⌋⌊⌊Nd⌋i⌋ 这个东西,可以发现它是一个关于 ⌊Nd⌋ 的函数。最后式子变成:
对于
f(n)=∑i=1n⌊ni⌋
,我们可以用同样的分块做法在
O(n√)
的时间内求得单个
f
值,那么就可以用
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50000
using namespace std;
int mu[50010],prm[50010],f[50010],T,n,m,p1,p2;
bool ext[50010];
long long ans;
void calc_mu(){
mu[1]=1;
for (int i=2;i<=N;i++){
if (ext[i]==false){
prm[++prm[0]]=i;
mu[i]=-1;
}
for (int j=1;j<=prm[0];j++){
if (i*prm[j]>N) break;
ext[i*prm[j]]=true;
if (i%prm[j]==0){
mu[i*prm[j]]=0;
break;
}else
mu[i*prm[j]]=-mu[i];
}
mu[i]+=mu[i-1];
}
}
int calc_f(int n){
int ans=0;
for (int i=1,j;i<=n;i=j+1){
j=n/(n/i);
if (j>n) j=n;
ans+=(n/i)*(j-i+1);
}
return ans;
}
int main()
{
calc_mu();
for (int i=1;i<=N;i++)
f[i]=calc_f(i);
scanf("%d",&T);
for (int time=1;time<=T;time++){
scanf("%d%d",&n,&m);
ans=0;p1=p2=1;
if (n>m) swap(n,m);
for (int i=1;;){
if (p1<=p2){
ans+=(long long)f[n/i]*f[m/i]*(mu[p1]-mu[i-1]);
i=p1+1;
}else{
ans+=(long long)f[n/i]*f[m/i]*(mu[p2]-mu[i-1]);
i=p2+1;
}
if (i>n) break;
p1=n/(n/i);p2=m/(m/i);
}
printf("%I64d\n",ans);
}
return 0;
}
偏偏在最后出现的补充说明
常用的经典的化式子方法一定要记熟,做题的时候能一下子想到才行。
其实现在看这个题还是挺简单的一个数论。。用的都是基础公式,化式子的方法也都非常经典。。但是当时不知道为啥瞪着题解看了一天愣是没看懂。。