【题解】洛谷P3768 简单的数学题[杜教筛]+两种欧反公式解析
(前排提示:这道题有两种方法)
这道题老抽了,很多人(包括我)想也不想的就直接莫比乌斯反演:
……巴拉巴拉的,这样,我就先不推了。
但是打住,冷静思考下,长这个样子的式子,
是不是还可以变成 呢?
(因为 ,严格推导后面问问题的时候有)
那接着就是:
但是 前面的这个,因为
太大,没法直接前缀和。
还得拿出来多造一个函数,搞一遍狄利克雷。
我把推导写这里,本质上就是 :
,
这个 略有麻烦,需要两个平方和数列求和相减(又得造个函数)。
最后再分块加速就好了。
说回 。
就像这道题一样,有时候,转换为欧拉函数会让我们省去很多步骤。
那到底什么时候可以转化为欧拉函数,什么时候会更方便?
还有上面链接那道题和这道题的转化后的形式为什么不一样,选哪一种更好?
先别急,我们先来解决第一个问题,也就是什么时候可以转化。
回想下,我们有 。也就是
。
而这个东西 ,莫反后刚好就是:
设 ,则有:
这个,其实就是欧拉函数。
也就是只要出现 ,就可以变成
。
(如果你认真做过莫反的题,会觉得上面的推导过程就像快乐老家一样)
那什么时候转化会更方便呢?其实大多数时候都会更好算,因为直接少了一个 for 循环。
(就是叫各位做数论题时看到 gcd 求和,就玩了命的推欧拉函数)
当然有且只有在 的时候可以用欧拉,其它像求 gcd 对数啊什么的还是老老实实莫反吧。
那为什么那道题和现在这道题的欧拉函数转化不一样呢?
很简单,我们比较 和
。
发现时间复杂度都是 ,而后者还要加个
的预处理。
但大家发现了吗? 和
是不同的!!也就是第一个公式改变了求和的变量,而第二个没有。
这个很重要!!!如果你需要求 的话,那你只能选第二种。
因为第一种把整个和式都大换洗了一遍,
化成 的话,你根本不知道
函数里面应该放什么。
而 还保留了最外面那个循环,
直接 就好。
我们这道题之所以选用 ,不仅是因为没有
函数,
还是因为 这坨很不好初始化,
加上 带到杜教筛里面就更灾难了。
讲了这么多,再复习这道题一遍公式:
这题还有很多细节错误特别阴,大家如果遇到问题,可以去洛谷该题讨论区看看别人的经验。
直接看代码吧:
#include<bits/stdc++.h>
using namespace std;
const int N=5e6+10;
typedef long long LL;
template<typename T> void qread(T &x){x=0; int f=1; char c=getchar();for(; !isdigit(c); c=getchar()) if(c=='-') f=-1;for(; isdigit(c); c=getchar()) x=x*10+(c-'0');x*=f;
}
LL P, n;
int phi[N], pr, prime[N]; //phi不能开longlong会炸
LL sp[N], invtwo, invsix;
bool v[N];
void init(){pr=0; memset(v, 0, sizeof(v));phi[1]=1;for(int i=2; i<=N-10; i++){if(!v[i]){pr++; prime[pr]=i;phi[i]=i-1;}for(int j=1; (j<=pr) && (i*prime[j]<=N-10); j++){int p=prime[j];v[i*p]=1;if(i%p==0){phi[i*p]=phi[i]*p;break;}else{phi[i*p]=phi[i]*phi[p];}} }sp[0]=0;for(int i=1; i<=N-10; i++){sp[i]=(sp[i-1]+1LL*phi[i] *i%P*i)%P;}
}
LL q_pow(LL a, LL b){LL res=1;while(b){if(b&1) res=res*a%P;a=a*a%P; b/=2;}return res;
}
LL powsum(LL x){ //平方和数列求和 x%=P;return x*(x+1)%P*(2*x+1)%P*invsix%P;
}
LL getsum(LL x){ //自然数数列求和平方(就是i^3立方和数列的求和) x%=P;LL t=x*(x+1)%P*invtwo%P;return t*t%P;
}
map<LL, LL> hsp;
LL calc(LL x){ //欧拉函数*i*i求和 if(x<=N-10) return sp[x];if(hsp[x]) return hsp[x];LL res=getsum(x)%P;for(LL i=2, j; i<=x; i=j+1){j=x/(x/i);LL t=((powsum(j)-powsum(i-1))%P+P)%P;res=((res-t*calc(x/i)%P)+P)%P;}return hsp[x]=res;
}
LL solve(LL x){LL res=0;for(LL i=1, j; i<=x; i=j+1){j=x/(x/i);LL t=getsum(x/i)%P;res=(res+((calc(j)-calc(i-1))%P+P)%P*t%P)%P;}return res;
}
int main(){qread(P); qread(n);invtwo=q_pow(2, P-2);invsix=q_pow(6, P-2);init();printf("%lld\n", solve(n));return 0;
}