NKOJ 3441 Lucas的数论(杜教筛)

P3441【HN Training 2015 Round5】lucas的数论

问题描述
这里写图片描述
数据范围

对于100%的数据:n<=1000000000


直接推式子,用到一个公式,这个公式也比较显然,就是根据定义直接得到,注意到不互质的数对乘积也一定会被乘积相同的一个互质数对算到。

Ans=i=1Nj=1Nτ(i×j)τ(i×j)=x|iy|j1[gcd(x,y)=1] A n s = ∑ i = 1 N ∑ j = 1 N τ ( i × j ) , 已 知 τ ( i × j ) = ∑ x | i ∑ y | j 1 [ g c d ( x , y ) = 1 ]

Ans=i=1Nj=1Nx|iy|jp|gcd(x,y)μ(p)=i=1Nj=1Np|gcd(i,j)τ(ip)τ(jp)μ(p) A n s = ∑ i = 1 N ∑ j = 1 N ∑ x | i ∑ y | j ∑ p | g c d ( x , y ) μ ( p ) = ∑ i = 1 N ∑ j = 1 N ∑ p | g c d ( i , j ) τ ( i p ) τ ( j p ) μ ( p )

Ans=p=1Nμ(p)i=1Npτ(i)j=1Npτ(j)=p=1Nμ(p)[i=1Npτ(i)]2 A n s = ∑ p = 1 N μ ( p ) ∑ i = 1 ⌊ N p ⌋ τ ( i ) ∑ j = 1 ⌊ N p ⌋ τ ( j ) = ∑ p = 1 N μ ( p ) [ ∑ i = 1 ⌊ N p ⌋ τ ( i ) ] 2

注意到后面是取整的形式,如果知道 τ(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=1d|iμ(d)=1 ∑ i = 1 n ∑ d | i μ ( d ) = 1 ,于是 ni=1nid=1μ(d)=1 ∑ i = 1 n ∑ d = 1 ⌊ n i ⌋ μ ( d ) = 1

i=1nμ(d)=1i=2nd=1niμ(d)i=1nμ(i)=M(n) ∑ i = 1 n μ ( d ) = 1 − ∑ i = 2 n ∑ d = 1 ⌊ n i ⌋ μ ( d ) , 令 ∑ i = 1 n μ ( i ) = M ( n )

M(n)=1i=2nM(ni) 那 么 M ( n ) = 1 − ∑ i = 2 n M ( ⌊ n i ⌋ )

上式显然可以分块迭代处理,预处理一部分后做到 O(n23) O ( n 2 3 ) ,从狄利克雷卷积的角度就是 μI=e μ ∗ I = e

对于 τ(i) τ ( i ) ,注意到 τ(i)=d|i1 τ ( i ) = ∑ d | i 1 ,即 τ=II τ = I ∗ I ,那么 μτ=Ie=I μ ∗ τ = I ∗ e = I ,即 d|nμ(nd)τ(d)=1 ∑ d | n μ ( n d ) τ ( d ) = 1 ,同样有 ni=1d|iμ(nd)τ(d)=ni=1nid=1μ(i)τ(d)=1 ∑ i = 1 n ∑ d | i μ ( n d ) τ ( d ) = ∑ i = 1 n ∑ d = 1 ⌊ n i ⌋ μ ( i ) τ ( d ) = 1 ,于是

i=1nτ(i)=1i=2nμ(i)d=1niτ(d)T(n)=i=1nτ(i) ∑ i = 1 n τ ( i ) = 1 − ∑ i = 2 n μ ( i ) ∑ d = 1 ⌊ n i ⌋ τ ( d ) , 令 T ( n ) = ∑ i = 1 n τ ( i )

T(n)=1i=2nμ(i)T(ni) 那 么 T ( n ) = 1 − ∑ i = 2 n μ ( i ) T ( ⌊ n i ⌋ )

上式显然还是分块迭代处理,但是需要用到 μ(i) μ ( i ) 的前缀和,复杂度不好说,但还是比较快的。

关于预处理一部分 tau(i) t a u ( i ) ,线性筛的时候需要增加两个数组,一个记录 i i 的最小质因子,另一个记录最小质因子的指数,由于线性筛每次筛掉一个数一定是用他的最小质因数,因此可以方便的转移,具体可以看代码。

事实上,求τ(i)的前缀和有更快的方法,因为 ni=1τ(i)=ni=1d|i1=ni=1nid=11=ni=1ni ∑ 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值