题解 P6788 【「EZEC-3」四月樱花】
本题解同步发表于我的cnblog
2020.8.29更新
被命题人@了,原来的做法已经过不去了……
这题是一道很不错的数论题,对整除分块的考察及其时间复杂度分析与优化较为深入。
算法考察
整除分块,常见数论函数性质。
算法分析
我们来推一推柿子。
显然
我们把
因此原式可推导如下:
改为枚举
改为枚举
我们设
整除分块基础——求解f(n)
考虑
通过观察,我们可以发现
单调性显然,取值个数简单证明一下:
因此我们考虑枚举这
求解
int get_f(int n){
int ret=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
// l,r表示区间[l,r]内的整数i,floor(n/i)相等(均为 n/l)
// l,r 的值的推导本处不展开
// 可以参考下面相关题目的题解
ret=(ret+1ll*(r-l+1)*(n/l))%mod;
// 区间[l,r]内有(r-l+1)个整数,它们的整除值都是 n/l
}
return ret;
}
相关题目:[CQOI2007]余数求和
回到原问题,现在我们已经可以用
整除分块进阶——整除分块嵌套
我们再观察式子:
我们发现
因此对于指数相同的部分,我们只需要知道底数的部分积即可。
观察底数部分的部分积。
发现这个值可以配合费马小定理求逆元快速求出。
类比整除分块的过程,我们枚举
这就是整除分块套整除分块。
类比杜教筛,时间复杂度为
算法优化
本算法还有进一步优化的空间。
继续类比杜教筛。在杜教筛中,我们预处理出部分函数值及其前缀和,从而将时间复杂度由
因数与倍数——f(n) 求法优化
我们考虑
因此我们把
其中
根据
现在我们考虑如何批量求出
注意到对每一个数
具体实现如下:
for(int i=1;i<=N;i++){
for(int j=1;i*j<=N;j++){
sig[i*j]++;
}
}
时间复杂度为
上述过程也可以用狄利克雷卷积解释,即
我们也不难看出
相关题目:[AHOI2005]约数研究
综上,我们可以用
由不等式相关知识得
代码实现
- 注意
2.5\times10^9 超出了int范围,要使用unsigned int; - 注意对指数上的整除分块求和模数应为
\varphi(p)=p-1 ; - 注意常数优化。
#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define ui unsigned
template <typename Tp> void read(Tp &x){
int fh=1;char c=getchar();x=0;
while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
}
ui n,mod;
ui ans=1;
ui ksm(ui B,ui P){ui ret=1;while(P){if(P&1)ret=1llu*ret*B%mod;B=1llu*B*B%mod;P>>=1;}return ret;}
ui f[maxn];
ui calc(ui x){//整除分块求f(n)
if(x<=1000000)return f[x];
ui ret=0;
for(ui l=1,r;l<=x;l=r+1){
r=x/(x/l);
ret=(ret+1ll*(r-l+1)*(x/l))%(mod-1);
}
return ret;
}
void preprocess(int N){//预处理求f(n)
for(int i=1;i<=N;i++){
for(int j=1;i*j<=N;j++){
f[i*j]++;
}
}
for(int i=1;i<=N;i++)f[i]=(f[i-1]+f[i])%mod;
}
signed main(){
read(n);read(mod);
preprocess(1000000);
for(ui l=1,r;l<=n;l=r+1){
r=n/(n/l);//整除分块嵌套
ui invr=1llu*l*ksm((r+1)%mod,mod-2)%mod;
invr=1llu*invr*invr%mod;
ans=1llu*ans*ksm(invr,calc(n/l))%mod;
}
printf("%u\n",ans);
return 0;
}
后记
本题解中附有一些相关题目,其本身可能难度不是很大,但其思想与本题相同。
对于求解不同的数论问题,枚举因数与枚举倍数各有所长,有时可能要反复转化。