【bzoj4176】Lucas的数论 【莫比乌斯反演】【杜教筛】

题目链接
题意: ni=1nj=1f(ij) ∑ i = 1 n ∑ j = 1 n f ( i j ) 1000000007 1000000007 的值。 f(n) f ( n ) 为n的约数个数。
题解:我们有一个结论: f(nm)=i|nj|m(gcd(i,j)==1) f ( n m ) = ∑ i | n ∑ j | m ( g c d ( i , j ) == 1 )
这是为什么呢?
考虑到 nm n m 的任何一个约数都可以表示成满足 i|n i | n j|m j | m imj i ∗ m j 的形式,上面的式子就是枚举i,j的值的过程。那为什么要求 gcd(i,j)==1 g c d ( i , j ) == 1 呢?如果有d满足 d|id|j d | i , d | j 那么 imj=ipmjp i ∗ m j = i p ∗ m j p ,重复计算了。所以我们有了上面的式子。
ans a n s
=ni=1nj=1f(ij) = ∑ i = 1 n ∑ j = 1 n f ( i j )
=ni=1nj=1a|ib|j(gcd(a,b)==1) = ∑ i = 1 n ∑ j = 1 n ∑ a | i ∑ b | j ( g c d ( a , b ) == 1 )
=ni=1nj=1a|ib|jk|gcd(a,b)μ(k) = ∑ i = 1 n ∑ j = 1 n ∑ a | i ∑ b | j ∑ k | g c d ( a , b ) μ ( k )
=ni=1nj=1a|ib|jk|a,k|bμ(k) = ∑ i = 1 n ∑ j = 1 n ∑ a | i ∑ b | j ∑ k | a , k | b μ ( k )
=nk=1μ(k)nka=1nkb=1naki=1nbkj=1 = ∑ k = 1 n μ ( k ) ∑ a = 1 ⌊ n k ⌋ ∑ b = 1 ⌊ n k ⌋ ∑ i = 1 ⌊ n a k ⌋ ∑ j = 1 ⌊ n b k ⌋
=nk=1μ(k)(nki=1nik)2 = ∑ k = 1 n μ ( k ) ( ∑ i = 1 ⌊ n k ⌋ ⌊ n i k ⌋ ) 2
于是我们用杜教筛算出 μ μ 的前缀和,再分块计算即可。
杜教筛
ni=1j|iμ(j)=1 ∑ i = 1 n ∑ j | i μ ( j ) = 1 ,因为只有i=1时有贡献1。
=> nj=1nji=1μ(j)=1 ∑ j = 1 n ∑ i = 1 ⌊ n j ⌋ μ ( j ) = 1
=> ni=1nij=1μ(j)=1 ∑ i = 1 n ∑ j = 1 ⌊ n i ⌋ μ ( j ) = 1
=> nj=1μ(j)=1ni=2nij=1μ(j) ∑ j = 1 n μ ( j ) = 1 − ∑ i = 2 n ∑ j = 1 ⌊ n i ⌋ μ ( j ) ,即把 i=1 i = 1 带入。
于是我们可以通过记忆化搜索求出前缀和。如果预先线性筛出较小的前缀和,时间复杂度大概为 O(n23) O ( n 2 3 ) 我也不会算
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5000005,M=1000033;
const ll mod=1000000007;
int n,p[N/10];
ll ans,tmp,mu[N];
bool vis[N];
struct HashSet{
    int cnt,head[M],to[M],nxt[M];
    ll v[M];
    bool count(int f){
        int x=f%M;
        for(int i=head[x];i;i=nxt[i]){
            if(to[i]==f){
                return true;
            }
        }
        return false;
    }
    ll get(int f){
        int x=f%M;
        for(int i=head[x];i;i=nxt[i]){
            if(to[i]==f){
                return v[i];
            }
        }
    }
    void set(int f,ll val){
        int x=f%M;
        to[++cnt]=f;
        nxt[cnt]=head[x];
        v[cnt]=val;
        head[x]=cnt;
    }
}s;
ll solve(int n){
    if(n<=5000000){
        return mu[n];
    }
    if(s.count(n)){
        return s.get(n);
    }
    ll res=1;
    for(int i=2,last;i<=n;i=last+1){
        last=n/(n/i);
        res-=solve(n/i)*(last-i+1);
        res%=mod;
    }
    s.set(n,res);
    return res;
}
ll calc(int n){
    ll res=0;
    for(int i=1,last;i<=n;i=last+1){
        last=n/(n/i);
        res+=(n/i)*(last-i+1)%mod;
        res%=mod;
    }
    return res;
}
int main(){
    mu[1]=1;
    for(int i=2;i<=5000000;i++){
        if(!vis[i]){
            p[++p[0]]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=p[0]&&i*p[j]<=5000000;j++){
            vis[i*p[j]]=true;
            if(i%p[j]){
                mu[i*p[j]]=-mu[i];
            }else{
                break;
            }
        }
    }
    for(int i=1;i<=5000000;i++){
        mu[i]+=mu[i-1];
        mu[i]%=mod;
    }
    scanf("%d",&n);
    for(int i=1,last;i<=n;i=last+1){
        last=n/(n/i);
        tmp=calc(n/i);
        ans+=tmp*tmp%mod*(solve(last)-solve(i-1))%mod;
        ans%=mod;
    }
    printf("%lld\n",(ans+mod)%mod);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值