题面
https://www.luogu.com.cn/problem/P4213
题解
前置知识
- 线性筛积性函数:https://www.cnblogs.com/zhoushuyu/p/8275530.html
- 莫比乌斯反演:《具体数学:计算机科学基础》4.9节
- 数论分块: https://www.cnblogs.com/xh092113/p/12269753.html
杜教筛
对于一些数论函数,杜教筛可以在低于线性的时间复杂度内求出其前缀和。
适用范围:对于数论函数f,g,h,如果g、h的前缀和能够(O(1))求解,并且有
[h=f{ imes}g$$(其中${ imes}$表示狄利克雷卷积)
那么,我们可以在低于线性的时间复杂度内求出f的前缀和。原理如下:(以下sf,sg,sh分别表示f,g,h的前缀和)
$$h(n)={sumlimits_{d1d2=n}}f(d1)g(d2)]
[{sumlimits_{i=1}^{n}}h(i)={sumlimits_{d1d2{leq}n}}f(d1)g(d2)
]
[sh(i)={sumlimits_{d2=1}^{n}}g(d2){sumlimits_{d1=1}^{{lfloor}{frac{n}{d1}}{
floor}}}f(d1)
]
[={sumlimits_{d=1}^{n}}g(d)sf({lfloor}{frac{n}{d}}{
floor})
]
将d=1一项提出,得
[g(1)sf(n)=sh(n)-{sumlimits_{i=2}^{n}g(i)sf({lfloor}{frac{n}{i}}{
floor})}
]
为了方便,不妨设(g(1)=1)。那么有
[sf(n)=sh(n)-{sumlimits_{i=2}^{n}}g(i)sf({lfloor}{frac{n}{i}}{
floor})
]
即可递归计算了。使用数论分块可以将计算(sf(n))的时间降到(O({sqrt{n}}))(不算递归下去的时间);并且,递归计算的所有子问题sf(n'),一定可以写成(n'={lfloor}{frac{n}{t}}{ floor})的形式,其中t是某个正整数。所以根据数论分块,记忆化后所有需要计算sf的下标只有({lfloor}{frac{n}{i}}{ floor}(1{leq}i{leq}n))去重后的那些数,计算的总时间是它们的平方根之和,也就是
[{sumlimits_{i=1}^{{sqrt{n}}}}{sqrt{i}}+{sumlimits_{i=1}^{sqrt{n}}}{sqrt{frac{n}{i}}}
]
两边分别做积分近似,可以得到这样的时间复杂度为(O(n^{frac{3}{4}}))。
如果函数f是积性函数,那么我们还可以进行进一步的优化:设置阈值M,将1~M的f值线性筛出来,如果计算sf(n)时(n{leq}M)就直接调用这些值,否则再进行记忆化搜索。
考虑M的最优值。如果(M<{sqrt{n}}),总时间为
[O(M)+{sumlimits_{i=M+1}^{sqrt{n}}}+{sumlimits_{i=1}^{sqrt{n}}}{sqrt{frac{n}{i}}}
]
发现第三项还是(O(n^{frac{3}{4}})),所以这样不行。
如果(M{geq}{sqrt{n}}),总时间为
[O(M)+{sumlimits_{i=1}^{frac{n}{M}}}{sqrt{frac{n}{i}}}
]
积分近似得第二项是(O({frac{n}{sqrt{m}}})),所以置(M=n^{frac{2}{3}})的时候,总时间复杂度最小,为(O(n^{frac{2}{3}}))。
代码
#include<bits/stdc++.h>
using namespace std;
#define N 2147483647
#define M 5000000
#define T 1300
#define ll long long
#define rg register
inline ll read(){
ll s = 0,ww = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
return s * ww;
}
inline void write(ll x){
if(x < 0)putchar('-'),x = -x;
if(x > 9)write(x / 10);
putchar('0' + x % 10);
}
ll pri[M/5+5];
ll pn;
bool isp[M+5];
ll smu[M+5],sphi[M+5];
inline void Eular(){
smu[1] = sphi[1] = 1;
for(rg ll i = 2;i <= M;i++)isp[i] = 1;
for(rg ll i = 2;i <= M;i++){
if(isp[i])pri[++pn] = i,smu[i] = -1,sphi[i] = i - 1;
for(rg ll j = 1;i * pri[j] <= M;j++){
isp[i*pri[j]] = 0;
if(i % pri[j])smu[i*pri[j]] = -smu[i],sphi[i*pri[j]] = sphi[i] * sphi[pri[j]];
else{
smu[i*pri[j]] = 0;
sphi[i*pri[j]] = sphi[i] * pri[j];
break;
}
}
}
for(rg ll i = 2;i <= M;i++)smu[i] += smu[i-1],sphi[i] += sphi[i-1];
}
ll n;
unordered_map<ll,ll>Hmu,Hphi;
inline ll sumphi(ll x){
if(x <= M)return sphi[x]; //小于M的部分,预处理出来,使用数组存储
if(Hphi.count(x))return Hphi[x]; //大于M的部分,记忆化搜索,使用Hash表存储
ll ans = 1ll * (ll)x * ((ll)x + 1) >> 1;
ll L,R = 1;
while(R < x){
L = R + 1,R = x / (x / L);
ans -= 1ll * (R - L + 1) * sumphi(x / L);
}
return Hphi[x] = ans;
}
inline ll summu(ll x){
if(x <= M)return smu[x]; //同上
if(Hmu.count(x))return Hmu[x]; //同上
ll ans = 1;
ll L,R = 1;
while(R < x){
L = R + 1,R = x / (x / L);
ans -= 1ll * (R - L + 1) * summu(x / L);
}
return Hmu[x] = ans;
}
int main(){
Eular();
ll c = read();
while(c--){
n = read();
write(sumphi(n)),putchar(' '),write(summu(n)),putchar('
');
}
return 0;
}