bzoj 3930: [CQOI2015]选数

题意:

在[L,H]中选n个可重复,有序的数,使这些数的gcd=k。

题解:

1A了很爽。
莫比乌斯反演+杜教筛。
先转化题意,设 lk=l1k+1   rk=rk
相当于在[lk,rk]中选n个互质的数。
ans=a1lk rka2lk rka1lk rk[gcd(a1,a2,an)=1]
反演一波就得到 ans=irkμ(i)(rkilk1i)n
然后杜教筛求 μ 的前缀和就可以了。杜教筛
code:

#include
#include
#include
#include
#include
#include
#define LL long long
using namespace std;
LL mu[5000010],prime[5000010];
bool v[5000010];
map<int,LL> sum;
int n,k,l,r,pr;
const LL mod=1000000007;
void pre()
{
    memset(v,true,sizeof(v));
    pr=0;mu[1]=1;
    for(int i=2;i<=5000000;i++)
    {
        if(v[i]) prime[++pr]=(LL)i,mu[i]=(LL)(-1);
        for(int j=1;j<=pr&&(LL)i*prime[j]<=5000000;j++)
        {
            v[i*prime[j]]=false;
            if(i%prime[j]==0){mu[i*prime[j]]=0;break;}
            mu[i*prime[j]]=-mu[i];
        }
    }
    mu[0]=0;
    for(int i=1;i<=5000000;i++) mu[i]=(mu[i]+mu[i-1])%mod;
}
LL solve(int n)
{
    if(n<=5000000) return mu[n];
    if(sum[n]) return sum[n];
    LL ans=1LL;int j;
    for(int i=2;i<=n;i=j+1)
    {
        j=n/(n/i);
        ans=(ans-solve(n/i)*(j-i+1))%mod;
    }
    sum[n]=ans;
    return ans;
}
LL work(LL a,int b)
{
    LL ans=1LL;
    while(b)
    {
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;b>>=1;
    }
    return ans;
}
int main()
{
    pre();sum.clear();
    scanf("%d %d %d %d",&n,&k,&l,&r);
    l=(l-1)/k+1;r/=k;//l=max(l,1);r=max(r,1);
    LL ans=0LL;int j;
    for(int i=1;i<=r;i=j+1)
    {
        //ans=(ans+mu[i]*work((r/i-(l-1)/i),n))%mod;
        //printf("%d %lld\n",i,ans);
        j=r/(r/i);
        if((l-1)/i) j=min(j,(l-1)/((l-1)/i));
        ans=(ans+(solve(j)-solve(i-1))*work((r/i-(l-1)/i),n))%mod;
    }
    printf("%lld",(ans+mod)%mod);
}

你可能感兴趣的:(数论,莫比乌斯反演,筛法)