题解[LuoguP6222]「P6156简单题」加强版
题解[LuoguP6222]「P6156简单题」加强版
加强版很好地体现了这个题的真正价值。(当然是指卡常
本题解给出了本题更详尽的推倒导和思考过程,思路与 CYJian 的类似,具体式子的个别地方换用了更易于理解的式子,可以看作是给数论新手的对莫反套路和欧拉筛套路的补充解释和技巧指导。
Problem
最多 \(10^4\) 组询问,每次询问给定 \(n\) ,求
对 \(2^{32}\) 取模。其中各个询问的 \(K\) 相等。\(n\le10^7\)
Solution
对 \(2^{32}\) 取模直接 unsigned int
自然溢出即可。
整体
考虑颓推式子:可以看到多次出现了 \(\gcd\) ,尝试莫反套路,先枚举 \(\gcd(i,j)=d\) ,然后将 \(d\) 提出到外层,最后对内层 \([gcd(i,j)=1]\) 莫反 。
按照我们的经验,可以想到将 \(t\) 也提出到外层并枚举 \(T=td\) 从而使得式子变成 \(n\) 个只关于 \(T\) 的函数的和(这种思想是很多数论题降低复杂度的关键,后面还会用)。
分离
\(f(x)\)
这时候感觉已经很难再整体推了,那么我们来观察一下式子。
令 \(f(T)=\sum_{d|T}d\mu^2(d)\mu(\frac Td)\) ,可以看出这是一堆积性函数做乘法和狄利克雷卷积,得到的结果依然是积性函数,可以考虑用欧拉筛预处理出来。
按照一般欧拉筛预处理积性函数的套路,先考虑在素数次幂上取值,再用积性函数的性质找到最小质因子转移。
那我们来讨论一下这个函数的取值(\(p\) 表示质数):
-
\(f(p^0)\):即 \(f(1)=1\) ,这个直接赋值就好,因为欧拉筛不考虑 \(1\)。
-
\(f(p)\):\(p\) 只有两个因子:\(p\) 和 \(1\) ,其中 \(\mu(p)=-1\),\(\mu(1)=1\)。\(f(p)=\mu^2(p)\mu(1)p+\mu^2(1)\mu(p)=p-1\) 。
-
\(f(p^2)\):\(p^2\) 有三个因子:\(p^2\) , \(p\) 和 \(1\) ,其中 \(\mu(p^2)=0\),\(\mu(p)=-1\),\(\mu(1)=1\),所以带 \(\mu(p^2)\) 的项都不会造成贡献。\(f(p^2)=\mu^3(p)p=-p\)
-
\(f(p^k)(k>2)\):考虑某一项 \(\mu^2(p^l)\mu(p^{k-l})p^l\):当 \(l\geq 2\) 时,\(\mu^2(p^l)=0\),该项无贡献;当 \(l<2\) 时,\(k-l\geq 2\),\(\mu(p^{k-l})=0\) 该项无贡献。\(f(p^k)=0(k>2)\)
所以我们可以在筛的时候判一下最小质因子的指数转移,并乘上 \(T^k\)(这个也可以筛出来),具体实现见代码。
\(g(x)\)
那我们看看剩下的这一部分:令 \(g(n)=\sum_{i=1}^n\sum_{j=1}^n(i+j)^K\)。这里我们又可以考虑枚举 \(s=i+j\) 来使得内层是只有一个参数的函数。
我们发现出现了非常难处理的 \(\max\) 和 \(\min\) 。考虑通过钦定判断的结果来消去这种难处理的基于判断的函数。
仔细观察,我们发现系数中同时包含了常数 \(n\) 和 \(1\),以及变量 \(s\) 。考虑把所有的常数系数都提出来。
此时已经将 \(g(n)\) 转化为几个 \(s^K\) 和 \(s^{K+1}\) 的前缀和了,这个可以用欧拉筛筛出来再做前缀和。
合并
目前我们已经得到 \(f(x)\) 和 \(g(x)\) 的 \(O(1)\) 查询方法,所以可以将整个函数作为一个整体来代入回原式寻找下一步的方向(这里看作 \(T^K\) 已经并入到 \(f(x)\) 中了)。
\(\sum\) 里套 \(\lfloor\frac nT\rfloor\) ,想到了什么?数论分块!不同的 \(g(\lfloor\frac nT\rfloor)\) 只会有 \(\sqrt n\) 种不同的取值,我们可以考虑预处理出 \(f(T)\) 的前缀和进行计算,具体实现见代码。
Core Code
附有详细注释,可放心食用!
typedef unsigned int UI;//自然溢出
const UI N=20000000,K=(1<<31);//这里 N 开双倍是因为 g(x) 中有 2n 的项。
UI n,d,nn;
UI p[N+10],tp,pk[N+10],pk1[N+10],f[N/2+10];//这里开一半是因为这题卡空间。
bool b[N+10];
inline void MakePrime(){
pk[1]=f[1]=1;
for(UI i=2;i<=nn;++i){
if(!b[i]){
p[++tp]=i,pk[i]=Pow(i,d);
if(i<=nn/2)
f[i]=i-1;//f 不需要开到 2n
}
for(UI j=1;j<=tp&&p[j]*i<=nn;++j){
UI t=p[j]*i;
b[t]=1;
pk[t]=pk[p[j]]*pk[i];//i^k 是完全积性函数
if(i%p[j])
f[t]=f[i]*f[p[j]];//如果互质就直接积性函数相乘
else{
UI tt=i/p[j];
if(tt%p[j])
f[t]=-p[j]*f[tt];//如果 i 中只有 1 个 p[j] 那么 p[j]*p[j] 和 i/p[j] 互质。
else
f[t]=0;//k>2 的情况
break;
}
}
}
for(UI i=1;i<=nn/2;++i)
f[i]=f[i-1]+f[i]*pk[i];//将 T^K 并入 f 中
for(UI i=1;i<=nn;++i)
pk1[i]=pk1[i-1]+pk[i]*i,pk[i]+=pk[i-1];//pk[i] 为 i^k 的前缀和,pk1[i] 为 i^(k+1) 的前缀和。
}
inline UI A(UI x){
return (2*x+1)*(pk[x<<1]-pk[x])+pk1[x]-(pk1[x<<1]-pk1[x])-pk[x];//g(x)
}
inline void Solve(){
UI ans=0;
n=read();
for(UI l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=A(n/l)*(f[r]-f[l-1]);//数论分块
}
printf("%u\n",ans);
}
int main(){
UI T=read();
nn=read()*2,d=read();//nn 表示最大的 n ,而筛法要求筛到 2n,所以将 nn*=2
MakePrime();
while(T--)
Solve();
return 0;
}
感谢观赏。