P3327 [SDOI2015] 约数个数和
[SDOI2015] 约数个数和
题目描述
设 \(d(x)\) 为 \(x\) 的约数个数,给定 \(n,m\),求
输入格式
输入文件包含多组测试数据。
第一行,一个整数 \(T\),表示测试数据的组数。
接下来的 \(T\) 行,每行两个整数 \(n,m\)。
输出格式
\(T\) 行,每行一个整数,表示你所求的答案。
样例 #1
样例输入 #1
2
7 4
5 6
样例输出 #1
110
121
提示
【数据范围】
对于 \(100\%\) 的数据,\(1\le T,n,m \le 50000\)。
\(d(x)\)的转化太艰难,考虑直接枚举约数进行统计。
\(d(ij)=\sum\limits_{x\mid i}\sum\limits_{y\mid j} [\gcd(x,y)=1]\)
....要是推不出上面这个式子这题的式子就打不开,不太能做。。。抽象。
当知识点记下来吧。 其实还挺好理解的。其实问题就是我怎么知道我要推它。。看起来很合理,但是这是知道了这个是结果后觉得合理。在不知道它是结果前,真的能够知道要用它吗?
\(\displaystyle\sum_{i=1}^{n}\displaystyle\sum_{j=1}^md(ij)\),用上面的式子转化,得到\(\displaystyle\sum_{i=1}^{n}\displaystyle\sum_{j=1}^m\sum\limits_{x\mid i}\sum\limits_{y\mid j} [\gcd(x,y)=1]\),再用\(\epsilon(n)=\displaystyle\sum_{d|n}\mu(d)\)
\(\displaystyle\sum_{i=1}^{n}\displaystyle\sum_{j=1}^m\sum\limits_{x\mid i}\sum\limits_{y\mid j} \displaystyle\sum_{d|x,d|y}\mu(d)\)
\(\displaystyle\sum_{i=1}^{n}\displaystyle\sum_{j=1}^m\sum\limits_{x\mid i}\sum\limits_{y\mid j} \displaystyle\sum_{d=1}^{d\leq min(x,y)}\mu(d)*[d|x]*[d|y]\)或者是\(\displaystyle\sum_{i=1}^{n}\displaystyle\sum_{j=1}^m\sum\limits_{x\mid i}\sum\limits_{y\mid j} \displaystyle\sum_{d=1}^{d\leq min(n,m)}\mu(d)*[d|x]*[d|y]\),发现后面的写法\(d\)与\(i,j\)无关,可以直接提前最后的d的枚举
\(\displaystyle\sum_{d=1}^{min(n,m)}\mu(d)\displaystyle\sum_{i=1}^{n}\displaystyle\sum_{j=1}^m\displaystyle\sum_{x|i}\displaystyle\sum_{y|j}[d|gcd(x,y)]\),然后后面的部分被动的枚举检测改为直接统计贡献。其实就是转为考虑满足\(\mu(d)\)能作为答案的是哪些部分
\(\displaystyle\sum_{d=1}^{min(n,m)}\mu(d)\displaystyle\sum_{i=1}^{\lfloor\frac n d\rfloor}\lfloor\frac n {di}\rfloor\displaystyle\sum_{j=1}^{\lfloor\frac m d\rfloor}\lfloor\frac m {dj}\rfloor\),整理一下就能得到前面的式子。
唯一的和莫反的板子的不同点就是\(d(ij)\)的转化。没了。不过后面的也不是完全常规,虽然思路是挺正常的。
后面的部分也不是普通的整除分块,循环本身是,内部又是,虽然两个重合起来不会脱离\(O(\sqrt n)\)的复杂度,但是写起来好麻烦。。。
从结论的式子上看,这题就和前面的题目明显不一样了。有很多对于莫反的变换的新的理解。约数枚举的提前其实并不是一个非常套路的东西,它有本身的规则,我之前都是感性理解的,导致问题其实挺大,因为不一定正确。本质就是如果里面的约数枚举的内容没有和前面的变量枚举相关的,那就直接用分配律提前就好。
所以其实完全可以把限制作为一个乘积写出来而不是追求所谓的"简洁"而写在循环内部。感觉是没有任何好处和方便的。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
const int N=100000;
int prime[N+1],cnt,mu[N+1],vis[N+1],sum[N+1];ll f[N+1];
void init()
{
mu[1]=1;
for(int i=2;i<=N;i++)
{
if(!vis[i])prime[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
for(int i=1;i<=N;i++)
{
sum[i]=sum[i-1]+mu[i];
}
for(int i=1;i<=N;i++)
{
for(int l=1,r=0;l<=i;l=r+1)
{
r=i/(i/l);
f[i]+=1LL*(i/l)*(r-l+1);
}
}
}
int main()
{
init();
int T=read();
while(T--)
{
int n=read(),m=read();
ll ans=0;
for(int l=1,r=0;l<=min(n,m);l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans+=(sum[r]-sum[l-1])*(f[n/l])*(f[m/l]);
}
printf("%lld\n",ans);
}
return 0;
}
居然卡我常...