题解:P13645 Totient with Divisors

· · 题解

题目传送门

题意

给你多组询问,求:

\sum_{i=1}^n\sum_{j=1}^m\varphi(i)\varphi(j)\sigma(ij)

其中 1\leq T,n,m\leq 10^5

分析

不妨设 n \leq m

首先我们肯定要把 \sigma(ij) 拆开。

然后这里有一个结论:

\sigma(ij)=\sum_{x\mid i}\sum_{y\mid j}\dfrac{iy}{x}[\gcd(x,y)=1]

::::success[该结论的简要证明] :::info[感性证明]{open} 我们有:

\sigma(x)=\prod_{i}(1+p_i+p_i^2+\cdots+p_i^{a_i})

每个质因子可以独立处理。

$\gcd(x,y)=1$ 保证了每个质因子 $x$ 和 $y$ 中有一个指数为 $0$。 那么 $\dfrac{iy}{x}$ 就可以取到一个质因子在 $ij$ 中的所有指数。 ::: :::info[理性证明]{open} $$\sigma(x)=\prod_{i}(1+p_i+p_i^2+\cdots+p_i^{a_i}) $$ 考虑每个质因子 $p$ 独立处理。 令 $v_p(n)$ 表示质因子 $p$ 整除 $n$ 的最高幂次。 由 $x\mid i,y\mid j$ 易得 $0 \leq v_p(x) \leq v_p(i),0 \leq v_p(y) \leq v_p(j)$。 由 $\gcd(x,y)=1$,易得 $ \min(v_p(x),v_p(y))=v_p(1)=0$。 - 当 $v_p(x)=0$ 且 $v_p(y) \neq 0$ 时,$v_p(\dfrac{iy}{x})=v_p(i)+v_p(y)$,恰好取得 $[v_p(i)+1,v_p(i)+v_p(j)=v_p(ij)]$ 每个数各一次。 - 当 $v_p(y)=0$ 时,$v_p(\dfrac{iy}{x})=v_p(i)-v_p(x)$,恰好取得 $[0,v_p(i)]$ 每个数各一次。 故 $v_p(\dfrac{iy}{x})$ 不重不漏的取了 $[0,v_p(ij)]$ 每个数各一次。 而所有质因子的每种组合都会被取一遍,故结论成立。 ::: :::: ~~最难点已过,恭喜你切掉一道黑题。~~ 接下来就是我们喜闻乐见的推式子时间: $$\sum_{i=1}^n\sum_{j=1}^m\varphi(i)\varphi(j)\sigma(ij)$$ 用上述结论把 $\sigma(ij)$ 替掉,得: $$\sum_{i=1}^n\sum_{j=1}^m\varphi(i)\varphi(j)\sum_{x\mid i}\sum_{y\mid j}\dfrac{iy}{x}[\gcd(x,y)=1]$$ 经典的拆 $\gcd$: $$ \sum_{i=1}^n\sum_{j=1}^m\varphi(i)\varphi(j)\sum_{x\mid i}\sum_{y\mid j}\dfrac{iy}{x}\sum_{d\mid \gcd(x,y)}\mu(d) $$ 交换求和顺序,把 $d$ 外提: $$ \sum_{d=1}^n \mu(d)(\sum_{d \mid x}^n\sum_{x\mid i}^n \dfrac{i\varphi(i)}{x})(\sum_{d\mid y}^m y\sum_{y \mid j}^m\varphi(j)) $$ 令 $x=dX,i=xI,y=dY,j=yJ$: $$ \sum_{d=1}^n \mu(d)(\sum_{X=1}^{\lfloor n/d \rfloor}\sum_{I=1}^{\lfloor n/dX \rfloor}I\varphi(dXI))(\sum_{Y=1}^{\lfloor m/d \rfloor} dY\sum_{J=1}^{\lfloor m/dY \rfloor}\varphi(dYJ)) $$ 把 $dY$ 中的 $d$ 往外提,交换 $J,Y$ 的求和顺序: $$ \sum_{d=1}^{n}d\mu(d)(\sum_{X=1}^{\lfloor n/d \rfloor}\sum_{I=1}^{\lfloor n/dX \rfloor}I\varphi(dXI))(\sum_{J=1}^{\lfloor m/d \rfloor}\sum_{Y=1}^{\lfloor m/dJ \rfloor}Y\varphi(dJY)) $$ 注意两个括号是相同的形式,我们令: $$ F(x,y)=\sum_{i=1}^x\sum_{j=1}^{\lfloor x/i \rfloor}j\varphi(ijy) $$ 令 $T=ij$: $$ \begin{aligned} F(x,y)&=\sum_{T=1}^x\varphi(Ty)\sum_{j \mid T}j\\ &=\sum_{T=1}^x\varphi(Ty)\sigma(T)\\ \end{aligned} $$ 注意到这东西可以递推: $$ F(x,y)=F(x-1,y)+\varphi(xy)\sigma(x) $$ 将其带入原式,得: $$ \sum_{d=1}^nd\mu(d) F(\lfloor \dfrac{n}{d} \rfloor,d) F(\lfloor \dfrac{m}{d} \rfloor,d) $$ 然后就可以用类似 [P4240](https://www.luogu.com.cn/problem/P4240) 的方法做了!具体的说: 然后你会发现这个时候 $F$ 里面是带 $d$ 的不能直接的数论分块。我们考虑平衡。 套路地,我们把整个式子换元换出来。我们令: $$ G(n,m,k)=\sum_{d=1}^kd\mu(d) F(\lfloor \dfrac{n}{d} \rfloor,d) F(\lfloor \dfrac{m}{d} \rfloor,d) $$ 然后我们发现当 $d$ 很大的时候 $\lfloor \dfrac{n}{d} \rfloor,\lfloor \dfrac{m}{d} \rfloor$ 很小,这是不是意味着我们可以预处理一部分 $G$? 首先我们可以用递推把所有的 $F$ 给预处理出来,时间复杂度是调和级数级别的即 $\mathcal{O}(n \ln n)$ 的。 设置一个阈值 $S$,$\lfloor \dfrac{n}{d} \rfloor \leq S$ 的部分预处理。用人话说,我们要把 $G(x,y,z)$ 中 $x \leq S$ 的所有 $G(x,y,z)$ 的值预处理出来。 因为第三维是可以递推 $\mathcal{O}(1)$ 求的,$x$ 有 $S$ 种取值,$y \times z \leq m$,预处理的时间复杂度是 $\mathcal{O}(mS)$ 的。 接下来我们就可以数论分块了,如果预处理过了数论分块算复杂度是 $\mathcal{O}(T \sqrt{n})$ 的,没预处理过暴力算。因为我们已经把 $\lfloor \dfrac{n}{d} \rfloor \leq S$ 的部分预处理了,所以剩下没预处理的一定满足 $d\le \lfloor\dfrac{n}{S+1}\rfloor$,时间复杂度为 $\mathcal{O}(\dfrac{nT}{S})$。 总时间复杂度 $\mathcal{O}(\dfrac{nT}{S}+T \sqrt{n}+mS+n \ln n)$。反正 $n,m$ 同阶,$S$ 取 $\sqrt{T}$ 可以达到时间复杂度 $O((n+m)\sqrt{T}+T\sqrt{n}+n \ln n)$。 ::::success[代码] ~~代码写的就是一坨大家不要拿我的代码做参考~~ ```cpp #include<bits/stdc++.h> using namespace std; const int N=1e5+5,L=405,TT=998244353; int read(){ int ret=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return ret*f; } void write(int x){ if(x<0)x=-x,putchar('-'); if(x>9)write(x/10); putchar(x%10+'0'); } int pri[N],top; int flg[N]; int mu[N],phi[N]; long long d[N],sp[N]; void init(int n){ mu[1]=1;phi[1]=1;d[1]=1; for(int i=2;i<=n;i++){ if(!flg[i]){ pri[++top]=i; phi[i]=i-1; mu[i]=-1; d[i]=i+1; sp[i]=i+1; } for(int j=1;j<=top;j++){ int pri_j=pri[j]; if(i*pri_j > n)break; flg[i*pri_j]=1; if(i%pri_j==0){ sp[i*pri_j]=sp[i]*pri_j+1; d[i*pri_j]=d[i]/sp[i]*sp[i*pri_j]; phi[i*pri_j]=phi[i]*pri_j; mu[i*pri_j]=0; break; } phi[pri_j*i]=phi[i]*phi[pri_j]; d[pri_j*i]=d[pri_j]*d[i]; mu[i*pri_j]=-mu[i]; sp[i*pri_j]=1+pri_j; } } for(int i=1;i<=n;i++)d[i]=d[i]%TT; }//筛 int T,n,m; int S; int f[L][N]; int af[N][505]; void make_f(){ for(int i=1;i<=S;i++){ for(int j=1;i*j<=(N-5);j++){ f[i][j]=((long long)f[i-1][j]+((long long)phi[i*j]*d[i]%TT))%TT; } } for(int j=1;j<=(N-5)/S;j++){//注意是 N/S 不是 S for(int i=1;i*j<=(N-5);i++){ af[i][j]=((long long)af[i-1][j]+((long long)phi[i*j]*d[i]%TT))%TT; } } }//预处理 f,懒得用 vector 所以我用了静态数组 vector<int> g[L][L]; int now; void make_g(){ for(int i=1;i<=S;i++){ for(int j=1;j<=S;j++){ g[i][j].push_back(0); for(int k=1;i*k<=(N-5) && j*k<=(N-5);k++){ g[i][j].push_back(((long long)g[i][j][k-1]+(long long)k*(long long)mu[k]%TT*(long long)f[i][k]%TT*f[j][k]%TT+TT)%TT); } } } }//预处理 g int calc(int n,int m){ int ret=0,l=1,r=0; while(l<=n){ r=min(n/(int)(n/l),m/(int)(m/l)); if(r>n)r=n; if((n/l)<=S && (m/l)<=S){ ret=((long long)ret+(long long)g[n/l][m/l][r]-g[n/l][m/l][l-1]+TT)%TT; }else{ for(int k=l;k<=r;k++)ret=((long long)ret+(long long)k*(long long)mu[k]%TT*(long long)af[n/l][k]%TT*af[m/l][k]%TT+TT)%TT; //暴力计算 } l=r+1; } return ret; } signed main(){ T=read();S=200; init(N-5); make_f();make_g(); while(T--){ n=read(),m=read(); if(n>m)swap(n,m); write(calc(n,m));putchar('\n'); } return 0; } ``` :::: ## 后记 为出题喜欢用经典结论的出题人献上由衷的祝福。 ~~我劝你们家里多备几个【数据删除】。~~