• 【BZOJ3930】选数(莫比乌斯反演,杜教筛)


    【BZOJ3930】选数(莫比乌斯反演,杜教筛)

    题面

    给定(n,K,L,R)
    问从(L~R)中选出(n)个数,使得他们(gcd=K)的方案数

    题解

    这样想,既然(gcd=K),首先就把区间缩小一下
    这样变成了(gcd=1)
    (f(i))表示(gcd)恰好为(i)的方案数
    那么,要求的是(f(1))
    (g(x)=sum_{d|x}f(d))
    所以(g(x))表示(x|gcd)的方案数
    这个不是很好求吗?

    所以一波莫比乌斯反演

    [f(1)=sum_{i=1}mu(i)g(i) ]

    好的,看看(g(x))怎么直接求
    现在可以取的区间范围是(L~R)
    要让(gcd)(x)的倍数
    区间的大小算一下,直接快速幂就行了

    然后(80)分到手啦

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define MOD 1000000007
    #define MAX 10000000
    inline int read()
    {
    	int x=0,t=1;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=-1,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return x*t;
    }
    int n,K,L,R;
    bool zs[MAX];
    int pri[MAX+1],tot,mu[MAX+1];
    void pre()
    {
    	zs[1]=true;mu[1]=1;
    	for(int i=2;i<=MAX;++i)
    	{
    		if(!zs[i])pri[++tot]=i,mu[i]=-1;
    		for(int j=1;j<=tot&&i*pri[j]<=MAX;++j)
    		{
    			zs[i*pri[j]]=true;
    			if(i%pri[j])mu[i*pri[j]]=-mu[i];
    			else break;
    		}
    	}
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int G(int x,int L,int R)
    {
    	L=(L-1)/x;R=R/x;
    	return fpow(R-L,n);
    }
    int main()
    {
    	pre();
    	n=read();K=read();L=read();R=read();
    	int ans=0;
    	for(int i=K;i<=R;i+=K)
    		ans+=mu[i/K]*G(i,L,R)%MOD,ans%=MOD;
    	printf("%d
    ",(ans+MOD)%MOD);
    	return 0;
    }
    
    

    现在的问题是(L,R)范围很大
    但是我们又要求一个大的(mu)
    怎么办嗷。。
    非线性时间诶。
    杜教筛??
    我们可以搞一下(mu)的前缀和就行了,
    这样两个相减就是(mu)
    (S(n)=sum_{i=1}^nmu(i))

    [g(1)S(n)=sum_{i=1}^n(g*mu)(i)-sum_{i=2}^{n}g(i)S(frac{n}{i}) ]

    (g(x)=1)

    [S(n)=1-sum_{i=2}^nS(frac{n}{i}) ]

    现在可以算出(mu)
    再回去看一下上面写的代码
    发现可以数论分块
    于是再来一次数论分块
    这题就没啦

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define MOD 1000000007
    #define MAX 10000000
    inline int read()
    {
    	int x=0,t=1;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=-1,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return x*t;
    }
    int n,K,L,R;
    bool zs[MAX];
    int pri[MAX+1],tot,mu[MAX+1],smu[MAX+1];
    map<int,int> M;
    void pre()
    {
    	zs[1]=true;mu[1]=1;
    	for(int i=2;i<=MAX;++i)
    	{
    		if(!zs[i])pri[++tot]=i,mu[i]=-1;
    		for(int j=1;j<=tot&&i*pri[j]<=MAX;++j)
    		{
    			zs[i*pri[j]]=true;
    			if(i%pri[j])mu[i*pri[j]]=-mu[i];
    			else break;
    		}
    	}
    	for(int i=1;i<=MAX;++i)smu[i]=smu[i-1]+mu[i];
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int SMu(int x)
    {
    	if(x<=MAX)return smu[x];
    	if(M[x])return M[x];
    	int ret=1;
    	for(int i=2,j;i<=x;i=j+1)
    	{
    		j=x/(x/i);
    		ret-=(j-i+1)*SMu(x/i);
    	}
    	return M[x]=ret;
    }
    int main()
    {
    	pre();
    	n=read();K=read();L=read();R=read();
    	L=(L-1)/K;R/=K;
    	int ans=0;
    	for(int i=1,j;i<=R;i=j+1)
    	{
    		j=R/(R/i);if(i<=L)j=min(j,L/(L/i));
    		ans+=(SMu(j)-SMu(i-1))*fpow(R/i-L/i,n)%MOD;
    		ans%=MOD;
    	}
    	printf("%d
    ",(ans+MOD)%MOD);
    	return 0;
    }
    
    
  • 相关阅读:
    css知识小结(更新中)
    vim的简易操作
    shell语言学习(更新中)
    An Introduction to C & GUI Programming -----Simon Long 学习笔记 1
    fread,fwrite(二)
    fread,fwrite(一)
    printf 打印颜色
    容斥原理及证明
    字典的认识和使用 day05
    列表和元祖的使用 day 04
  • 原文地址:https://www.cnblogs.com/cjyyb/p/8303813.html
Copyright © 2020-2023  润新知