题解 P4240 【毒瘤之神的考验】
注:集合
P4240 毒瘤之神的考验解题报告:
更好的阅读体验
题意
- 给定
n,m ,求\sum_{i=1}^n\sum_{j=1}^m \varphi(i\cdot j) ; - 多组数据,数据组数
T\leqslant 10^4 。 - 数据范围:
1\leqslant n,m\leqslant 10^5 。
分析
莫比乌斯反演结合根号分治好题。
首先,欧拉函数有一个性质:
证明如下:
由欧拉函数的公式有:
我们根据容斥的思想可以得到:
设
然后枚举
这个式子并不好处理,可以先设
但是
考虑根号分治,我们设一个阈值
- 对于大于
\lfloor\frac{n}{t}\rfloor 的答案部分,可以使用整除分块加S 的预处理来解决问题,对于整除分块中的区间[l,r] ,我们答案为S(\lfloor\frac{n}{l}\rfloor,\lfloor\frac{m}{l}\rfloor,r)-S(\lfloor\frac{n}{l}\rfloor,\lfloor\frac{m}{l}\rfloor,l-1) ,因为l,r 均大于t ,所以我们只需要预处理x\leqslant t,y\leqslant t,z\leqslant n 的所有S(x,y,z) ,预处理复杂度O(n\cdot t^2) ,查询复杂度O(\sqrt{n}) 。(注意:如果直接开数组空间会炸,我们可以用\text{vector} 来实现动态申请S 的空间) - 对于小于等于
\lfloor\frac{n}{t}\rfloor 的答案部分,可以直接暴力算答案,即暴力求\sum_{i=1}^{\lfloor\frac{n}{t}\rfloor}f(i)\cdot g(i,\lfloor\frac{n}{i}\rfloor)\cdot g(i,\lfloor\frac{m}{i}\rfloor) ,因为里面需要用到的g(x,y) 满足x\leqslant \lfloor\frac{n}{t}\rfloor,y\leqslant t ,所以我们可以同样预处理满足上述条件的所有g ,预处理复杂度为O(n) ,查询为O(\lfloor\frac{n}{t}\rfloor) ,同样也要动态申请空间。
总复杂度为
代码
#include<stdio.h>
#include<vector>
#define int long long
using namespace std;
const int maxn=100005,mod=998244353,maxt=55;
int T,n,m,cnt,t;
int a[maxn],p[maxn],miu[maxn],phi[maxn],nphi[maxn],f[maxn];
vector<int>g[maxn],S[maxt][maxt];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)
res=res*a%mod;
a=a*a%mod,b>>=1;
}
return res;
}
void init(){
t=50;
p[1]=miu[1]=phi[1]=1;
for(int i=2;i<maxn;i++){
if(p[i]==0)
a[++cnt]=i,miu[i]=-1,phi[i]=i-1;
for(int j=1;j<=cnt;j++){
if(i*a[j]>=maxn)
break;
p[i*a[j]]=1;
if(i%a[j]==0){
miu[i*a[j]]=0;
phi[i*a[j]]=phi[i]*a[j];
break;
}
miu[i*a[j]]=-miu[i];
phi[i*a[j]]=phi[i]*(a[j]-1);
}
}
for(int i=1;i<maxn;i++)
nphi[i]=ksm(phi[i],mod-2);
for(int i=1;i<maxn;i++)
for(int j=1;i*j<maxn;j++)
f[i*j]=(f[i*j]+i*nphi[i]%mod*miu[j])%mod;
for(int i=1;i<maxn;i++){
g[i].push_back(0);
for(int j=1;i*j<maxn;j++)
g[i].push_back((g[i][j-1]+phi[i*j])%mod);
}
for(int i=1;i<=t;i++)
for(int j=1;j<=t;j++){
S[i][j].push_back(0);
for(int k=1;k*max(i,j)<maxn;k++)
S[i][j].push_back((S[i][j][k-1]+f[k]*g[k][i]%mod*g[k][j]%mod)%mod);
}
}
int solve(int n,int m){
int res=0,l,r;
for(int i=1;i<=max(n,m)/t;i++)
res=(res+f[i]*g[i][n/i]%mod*g[i][m/i]%mod)%mod;
l=max(n,m)/t+1;
while(l<=min(n,m)){
r=min(n/(n/l),m/(m/l));
res=(res+(S[n/l][m/l][r]-S[n/l][m/l][l-1]+mod)%mod)%mod;
l=r+1;
}
return res;
}
signed main(){
init();
scanf("%lld",&T);
while(T--){
scanf("%lld%lld",&n,&m);
printf("%lld\n",solve(n,m));
}
return 0;
}