P3441【HN Training 2015 Round5】lucas的数论
问题描述
数据范围
对于100%的数据:n<=1000000000
直接推式子,用到一个公式,这个公式也比较显然,就是根据定义直接得到,注意到不互质的数对乘积也一定会被乘积相同的一个互质数对算到。
注意到后面是取整的形式,如果知道
τ(i)
τ
(
i
)
和
μ(i)
μ
(
i
)
的前缀和,那么可以分块在
O(n−−√)
O
(
n
)
内求解
考虑如何快速求出
τ(i)
τ
(
i
)
的前缀和与
μ(i)
μ
(
i
)
的前缀和,考虑杜教筛。
对于
μ(i)
μ
(
i
)
,注意到
∑d|nμ(d)=[n==1]
∑
d
|
n
μ
(
d
)
=
[
n
==
1
]
,那么有
∑ni=1∑d|iμ(d)=1
∑
i
=
1
n
∑
d
|
i
μ
(
d
)
=
1
,于是
∑ni=1∑⌊ni⌋d=1μ(d)=1
∑
i
=
1
n
∑
d
=
1
⌊
n
i
⌋
μ
(
d
)
=
1
上式显然可以分块迭代处理,预处理一部分后做到 O(n23) O ( n 2 3 ) ,从狄利克雷卷积的角度就是 μ∗I=e μ ∗ I = e
对于
τ(i)
τ
(
i
)
,注意到
τ(i)=∑d|i1
τ
(
i
)
=
∑
d
|
i
1
,即
τ=I∗I
τ
=
I
∗
I
,那么
μ∗τ=I∗e=I
μ
∗
τ
=
I
∗
e
=
I
,即
∑d|nμ(nd)τ(d)=1
∑
d
|
n
μ
(
n
d
)
τ
(
d
)
=
1
,同样有
∑ni=1∑d|iμ(nd)τ(d)=∑ni=1∑⌊ni⌋d=1μ(i)τ(d)=1
∑
i
=
1
n
∑
d
|
i
μ
(
n
d
)
τ
(
d
)
=
∑
i
=
1
n
∑
d
=
1
⌊
n
i
⌋
μ
(
i
)
τ
(
d
)
=
1
,于是
上式显然还是分块迭代处理,但是需要用到 μ(i) μ ( i ) 的前缀和,复杂度不好说,但还是比较快的。
关于预处理一部分 tau(i) t a u ( i ) ,线性筛的时候需要增加两个数组,一个记录 i i 的最小质因子,另一个记录最小质因子的指数,由于线性筛每次筛掉一个数一定是用他的最小质因数,因此可以方便的转移,具体可以看代码。
事实上,求的前缀和有更快的方法,因为 ∑ni=1τ(i)=∑ni=1∑d|i1=∑ni=1∑⌊ni⌋d=11=∑ni=1⌊ni⌋ ∑ i = 1 n τ ( i ) = ∑ i = 1 n ∑ d | i 1 = ∑ i = 1 n ∑ d = 1 ⌊ n i ⌋ 1 = ∑ i = 1 n ⌊ n i ⌋ ,直接分块+预处理可以做到 O(n−−√) O ( n ) ,最终复杂度 O(n34) O ( n 3 4 ) ,复杂度并不会证,看看就好。
代码(杜教筛求 τ(i) τ ( i ) ):
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<cmath>
#define N 12345678
#define ll long long
using namespace std;
const ll mod=1e9+7;
map<ll,ll>Mu,G;
ll n,K,tot,P[N],g[N],mu[N],pc[N],pd[N];
void EU()
{
ll i,j;mu[1]=g[1]=1;
for(i=2;i<=K;i++)
{
if(!g[i])P[++tot]=i,mu[i]=-1,g[i]=2,pc[i]=1,pd[i]=tot;
for(j=1;j<=tot&&i*P[j]<=K;j++)
{
if(i%P[j])
{
pc[i*P[j]]=1;
pd[i*P[j]]=j;
mu[i*P[j]]=-mu[i];
g[i*P[j]]=g[i]<<1;
}
else
{
pc[i*P[j]]=j==pd[i]?pc[i]+1:1;
pd[i*P[j]]=j;
g[i*P[j]]=j==pd[i]?(g[i]/(pc[i]+1)*(pc[i]+2)):(g[i]<<1);
}
}
}
for(i=2;i<=K;i++)g[i]+=g[i-1],mu[i]+=mu[i-1],g[i]%=mod;
}
ll Gmu(ll x)
{
if(x<=K)return mu[x];
if(Mu[x])return Mu[x];
ll i,j,ans=1;
for(i=2;i<=x;i=j+1)
{
j=x/(x/i);
ans-=(j-i+1)*Gmu(x/i);
}
return Mu[x]=ans;
}
ll Gg(ll x)
{
if(x<=K)return g[x];
if(G[x])return G[x];
ll i,j,ans=x;
for(i=2;i<=x;i=j+1)
{
j=x/(x/i);
ans-=(Gmu(j)-Gmu(i-1))*Gg(x/i)%mod;ans%=mod;
}
return G[x]=ans;
}
int main()
{
ll ans=0,i,j;
scanf("%lld",&n);
K=pow(n,0.66);EU();
for(i=1;i<=n;i=j+1)
{
j=n/(n/i);
ans+=(Gmu(j)-Gmu(i-1))*Gg(n/i)%mod*Gg(n/i)%mod;ans%=mod;
}
printf("%lld",(ans+mod)%mod);
}
另附 O(n−−√) O ( n ) 求 τ τ 做法,实测并快不了多少,但如果是算单个应该快很多。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<cmath>
#define N 12345678
#define ll long long
using namespace std;
const ll mod=1e9+7;
map<ll,ll>Mu,G;
ll n,K,tot,P[N],g[N],mu[N],pc[N],pd[N];
void EU()
{
ll i,j;mu[1]=g[1]=1;
for(i=2;i<=K;i++)
{
if(!g[i])P[++tot]=i,mu[i]=-1,g[i]=2,pc[i]=1,pd[i]=tot;
for(j=1;j<=tot&&i*P[j]<=K;j++)
{
if(i%P[j])
{
pc[i*P[j]]=1;
pd[i*P[j]]=j;
mu[i*P[j]]=-mu[i];
g[i*P[j]]=g[i]<<1;
}
else
{
pc[i*P[j]]=j==pd[i]?pc[i]+1:1;
pd[i*P[j]]=j;
g[i*P[j]]=j==pd[i]?(g[i]/(pc[i]+1)*(pc[i]+2)):(g[i]<<1);
}
}
}
for(i=2;i<=K;i++)g[i]+=g[i-1],mu[i]+=mu[i-1],g[i]%=mod;
}
ll Gmu(ll x)
{
if(x<=K)return mu[x];
if(Mu[x])return Mu[x];
ll i,j,ans=1;
for(i=2;i<=x;i=j+1)
{
j=x/(x/i);
ans-=(j-i+1)*Gmu(x/i);
}
return Mu[x]=ans;
}
ll Gg(ll x)
{
if(x<=K)return g[x];
if(G[x])return G[x];
ll ans=0,i,j;
for(i=1;i<=x;i=j+1)
{
j=x/(x/i);
ans+=(j-i+1)*(x/i)%mod;ans%=mod;
}
return G[x]=ans;
}
int main()
{
ll ans=0,i,j;
scanf("%lld",&n);
K=pow(n,0.66);EU();
for(i=1;i<=n;i=j+1)
{
j=n/(n/i);
ans+=(Gmu(j)-Gmu(i-1))*Gg(n/i)%mod*Gg(n/i)%mod;ans%=mod;
}
printf("%lld",(ans+mod)%mod);
}