[BZOJ3930][CQOI2015]选数(莫比乌斯反演+杜教筛)

题目描述

传送门

题解

我tm从头到尾竟然都记了一个错误的反演公式…
f(n) 表示选出gcd为n的有多少种方案
F(n) 表示选出gcd为n的倍数的有多少种方案
也就是 F(n)=n|df(d)
那么利用反演公式可以得到 f(n)=n|dμ(dn)F(d)
现在就是要求 f(k)=k|dμ(dk)F(d)
首先考虑 F(d) 如何求,很显然若[l..r]范围内d的倍数的个数为x的话,答案应该为 xn
那么 F(d) 不就是 (rdl1d)n
代入之后 f(k)=k|dμ(dk)(rdl1d)n
d=dk
那么 f(k)=d=1rkμ(d)(rkdl1kd)n
这个式子看起来比较奇怪,实际上预处理 μ 的前缀和了之后是可以根号求的,因为取值是根号级别
关于 μ 的前缀和…直接裸上杜教筛!
怎么跑这么快…

代码

#include
#include
#include
#include
#include
#include
using namespace std;
#define N 1000005
#define Mod 1000000007
#define LL long long

int a,init=1000000,n,m,k,l,r,d;
int p[N],prime[N],mu[N];
map <int,int> summu;
LL ans;

void get()
{
    mu[1]=1;
    for (int i=2;i<=init;++i)
    {
        if (!p[i])
        {
            prime[++prime[0]]=i;
            mu[i]=-1;
        }
        for (int j=1;j<=prime[0]&&i*prime[j]<=init;++j)
        {
            p[i*prime[j]]=1;
            if (i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            else mu[i*prime[j]]=-mu[i];
        }
    }
    for (int i=1;i<=init;++i) mu[i]+=mu[i-1];
}
LL fast_pow(LL a,int p)
{
    LL ans=1;
    for (;p;p>>=1,a=a*a%Mod)
        if (p&1)
            ans=ans*a%Mod;
    return ans;
}
LL getmu(int n)
{
    if (n<=init) return mu[n];
    if (summu[n]) return summu[n];
    LL tmp=1;
    for (int i=2,j=0;i<=n;i=j+1)
    {
        j=n/(n/i);
        tmp-=getmu(n/i)*(LL)(j-i+1);
    }
    return summu[n]=tmp;
}
int main()
{
    get();
    scanf("%d%d%d%d",&a,&k,&l,&r);
    n=r/k;m=(l-1)/k;
    for (int i=1,j=0;i<=n;i=j+1)
    {
        if (m/i) j=min(n/(n/i),m/(m/i));
        else j=n/(n/i);
        ans+=(LL)(getmu(j)-getmu(i-1))*fast_pow(n/i-m/i,a)%Mod;
        ans%=Mod;
    }
    ans=(ans+Mod)%Mod;
    printf("%lld\n",ans);
}

你可能感兴趣的:(题解,省选,莫比乌斯反演)