题目链接:传送门
前置技能:
解题思路:
点亮前置技能就珂以发现这是裸题……
首先杜教筛要卷积一个函数。
先考虑
S
(
n
)
=
Σ
i
=
1
n
φ
(
i
)
S(n)=\Large\Sigma\large_{i=1}^{n}φ(i)
S(n)=Σi=1nφ(i)的情况:
发现有一条式子叫做
φ
∗
I
=
i
d
φ*I=id
φ∗I=id。
所以让两边卷积一个
I
I
I,得到
(
I
∗
S
)
(
n
)
=
Σ
i
=
1
n
i
d
(
i
)
=
n
∗
(
n
+
1
)
2
(I*S)(n)=\Large\Sigma\large_{i=1}^{n}id(i)=\frac{n*(n+1)}{2}
(I∗S)(n)=Σi=1nid(i)=2n∗(n+1)
按照杜教筛的套路乱搞一波(此处可直接套杜教筛的结论),得到:
S
(
n
)
=
Σ
i
=
1
n
(
I
∗
φ
)
(
i
)
−
Σ
i
=
2
n
I
(
i
)
S
(
n
i
)
S(n)=\Large\Sigma\large_{i=1}^n(I*φ)(i)-\Large\Sigma\large_{i=2}^nI(i)S(\frac{n}{i})
S(n)=Σi=1n(I∗φ)(i)−Σi=2nI(i)S(in)
=
n
∗
(
n
+
1
)
2
−
Σ
i
=
2
n
S
(
n
i
)
=\large\frac{n*(n+1)}{2}-\Large\Sigma\large_{i=2}^nS(\frac{n}{i})
=2n∗(n+1)−Σi=2nS(in)
然后整除分块+记忆化乱搞即可。
对于 S ( n ) = Σ i = 1 n μ ( i ) S(n)=\Large\Sigma\large_{i=1}^{n}μ(i) S(n)=Σi=1nμ(i)的情况,也类似地套模板即可。
奇技淫巧
发现递归的时候
n
n
n很大,记忆化不太好搞,这里需要map奇技淫巧来解决。
递归时需要记忆化的值只有
⌊
n
2
⌋
,
⌊
n
3
⌋
,
.
.
.
.
\lfloor\frac{n}{2}\rfloor,\lfloor\frac{n}{3}\rfloor,....
⌊2n⌋,⌊3n⌋,....(这个先不证明)
因为当递归传递的值小于
n
2
3
n^{\frac{2}{3}}
n32(一般设为
1
e
6
1e6
1e6左右)时,是直接线性筛出来的,所以珂以考虑对于
x
x
x,把需要记忆化的
v
a
l
val
val存在
⌊
n
x
⌋
\lfloor\frac{n}{x}\rfloor
⌊xn⌋的位置。
因为
x
>
1
0
6
x>10^6
x>106(否则调用线性筛的结果直接返回),
n
n
n在
1
0
10
10^{10}
1010左右,所以
⌊
n
x
⌋
\lfloor\frac{n}{x}\rfloor
⌊xn⌋在
1
0
4
10^4
104以下。
这样就珂以不用开一个
1
0
10
10^{10}
1010的数组或是map来记忆化了。
这里放一张图:
但是这样会不会冲突呢?比如
⌊
⌊
n
2
⌋
3
⌋
\Large\lfloor\frac{\lfloor\frac{n}{2}\rfloor}{3}\rfloor
⌊3⌊2n⌋⌋会不会与
⌊
n
6
⌋
\large\lfloor\frac{n}{6}\rfloor
⌊6n⌋不相等,导致需要记忆化的位置不只有
⌊
n
x
⌋
\lfloor\frac{n}{x}\rfloor
⌊xn⌋呢?
这里需要引进一条玄学定理,叫做
⌊
⌊
n
a
⌋
b
⌋
=
⌊
n
a
b
⌋
\Large\lfloor\frac{\lfloor\frac{n}{a}\rfloor}{b}\rfloor=\lfloor\frac{n}{ab}\rfloor
⌊b⌊an⌋⌋=⌊abn⌋。
这里就不证明了,珂以看OIWiki-莫比乌斯反演中的证明qwq
根据引理珂以看出不存在
⌊
n
x
⌋
(
x
≤
n
)
\lfloor\frac{n}{x}\rfloor(x\le n)
⌊xn⌋(x≤n)之外需要记忆化的位置。
所以如上乱搞即可qwq
另外,这题卡常数,要把两个前缀和在一个函数里面求出来。
update:
今天刚发现这个优化方法有适用范围:
如果对于很多个
x
x
x,都要求出
S
(
x
)
S(x)
S(x)的值,就不适用了qwq
因为每次存储的位置是
⌊
n
i
⌋
\lfloor\frac{n}{i}\rfloor
⌊in⌋,和
n
n
n有关
如果每次都强行用这种优化方法,每次要memset一下,时间复杂度就爆了qwq
举个栗子:洛谷P3172 CQOI2015 选数,即bzoj3930 CQOI2015 选数
毒瘤代码
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define mod 1000000000
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
const int Size=2000005;
ll tot,mu[Size],phi[Size],prime[Size];
ll summu[Size],sumphi[Size];
bool vis[Size];
void getp(int maxn) {
phi[1]=mu[1]=1;
phi[0]=0;
for(re i=2; i<=maxn; i++) {
if(!vis[i]) {
prime[++tot]=i;
mu[i]=-1;
phi[i]=i-1;
}
for(re j=1; j<=tot; j++) {
int now=i*prime[j];
if(now>maxn) break;
vis[now]=true;
if(i%prime[j]==0) {
mu[now]=0;
phi[now]=phi[i]*prime[j];
break;
}
phi[now]=phi[i]*phi[prime[j]];
mu[now]=-mu[i];
}
}
for(re i=1; i<=maxn; i++) {
summu[i]=summu[i-1]+mu[i];
sumphi[i]=sumphi[i-1]+phi[i];
}
}
ll n,m1[Size],m2[Size];
bool vis1[Size],vis2[Size];
pair<ll,ll> getans(ll x) {
if(x<=2000000) return make_pair(sumphi[x],summu[x]);
int t=n/x;
if(vis1[t]) return make_pair(m1[t],m2[t]);
ll sum1=0,sum2=0,lst;
for(ll i=2; i<=x; i=lst+1) {
lst=x/(x/i);
pair<ll,ll> tmp=getans(x/i);
sum1+=(lst-i+1)*tmp.first;
sum2+=(lst-i+1)*tmp.second;
}
vis1[t]=true;
m1[t]=(x*(x+1ll)>>1ll)-sum1;
m2[t]=1ll-sum2;
return make_pair(m1[t],m2[t]);
}
int main() {
getp(2000000);
int T=read();
while(T--) {
n=read();
memset(vis1,0,sizeof(vis1));
memset(vis2,0,sizeof(vis2));
pair<ll,ll> ans=getans(n);
printf("%lld %lld\n",ans.first,ans.second);
}
return 0;
}
/*
1
2147483647
*/