【bzoj3930】[CQOI2015]选数 【莫比乌斯反演】【杜教筛】

题目传送门
题意:求从区间[L,H]LH为整数)中选取N个整数,使它们的gcdK的方案总数模1000000007的值。
题解:我们令l=L1Kr=HK。则原问题等价于求从区间[l,r]中选取N个整数,使它们的gcd1的方案总数模1000000007的值。
我们令F(i)为选数的gcd为i的倍数的方案总数,则显然F(i)=(rili)N
我们令f(i)为选数的gcd为i的方案总数,则显然F(i)=i|df(d)
莫比乌斯反演可得f(i)=i|dμ(di)F(d)
把i=1带入,得f(1)=d=1rμ(d)(rdld)N
于是这个式子我们可以先杜教筛求出μ的前缀和,再分块计算。
杜教筛是什么?
杜教筛点这里
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1000005,M=1000033;
const ll mod=1000000007;
int n,k,l,r,p[N];
ll ans,mu[N];
bool vis[N];
ll fastpow(ll a,ll x){
    a%=mod;
    ll res=1;
    while(x){
        if(x&1){
            res=res*a%mod;
        }
        x>>=1;
        a=a*a%mod;
    }
    return res;
}
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<=1000000){
        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)%mod;
        res%=mod;
    }
    s.set(n,res);
    return res;
}
int main(){
    scanf("%d%d%d%d",&n,&k,&l,&r);
    l=(l-1)/k;
    r/=k;
    if(!r){
        puts("0");
        return 0;
    }
    mu[1]=1;
    for(int i=2;i<=1000000;i++){
        if(!vis[i]){
            p[++p[0]]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=p[0]&&i*p[j]<=1000000;j++){
            vis[i*p[j]]=true;
            if(i%p[j]){
                mu[i*p[j]]=-mu[i];
            }else{
                break;
            }
        }
    }
    for(int i=2;i<=1000000;i++){
        mu[i]+=mu[i-1];
        mu[i]%=mod;
    }
    for(int i=1,last;i<=r;i=last+1){
        if(l/i){
            last=min(r/(r/i),l/(l/i));
        }else{
            last=r/(r/i);
        }
        ans+=fastpow(r/i-l/i,n)*(solve(last)-solve(i-1))%mod;
        ans%=mod;
    }
    printf("%lld\n",(ans+mod)%mod);
    return 0;
}
posted @ 2018-06-05 17:19  ez_2016gdgzoi471  阅读(42)  评论(0编辑  收藏  举报