与扩展埃氏筛(min_25筛?)玩耍

这篇博客是一年前写的……那时这东西好像还是个Cai佬偷偷教我们的黑科技但现在似乎已经人尽皆知了……

Cai佬说这东西叫做扩展埃氏筛,但似乎它和min25筛是一个东西?

与素数玩耍

例题: loj6235 区间素数个数

s u m ( x ) sum(x) sum(x)表示小于等于x的素数个数。

假设我很蠢(这件事根本不用假设好吗),连10以内的素数有哪些都不知道,只知道1不是素数。那么我就会令 s u m ( x ) = x − 1 sum(x)=x-1 sum(x)=x1

然后我就会做一些事情来扔掉合数,于是我从小到大处理素数。假设我在处理素数 p p p,并且想要扔掉 s u m ( i ) sum(i) sum(i)中以p为最小素因子的合数,我就会看向 s u m ( ⌊ i p ⌋ ) sum(\lfloor \frac{i}{p} \rfloor) sum(pi),由于前期扔掉了一些数,这个里面应该只有素数和素因子大于等于p的数,排除掉小于p的素数,剩下的数乘以p应该就是我们这一轮操作要扔掉的合数,也就是说, s u m ( i ) − = s u m ( ⌊ i p ⌋ ) − s u m ( p − 1 ) sum(i)-=sum(\lfloor \frac{i}{p} \rfloor)-sum(p-1) sum(i)=sum(pi)sum(p1)

f 1 ( x ) = s u m ( x ) f1(x)=sum(x) f1(x)=sum(x), f 2 ( x ) = s u m ( ⌊ n x ⌋ ) f2(x)=sum(\lfloor \frac{n}{x} \rfloor) f2(x)=sum(xn)。f1和f2都只用开根号级别的空间。

那么我们就可以灵活运用f1和f2来完成筛法了。

复杂度(听说)可以近似拟合为 O ( n 3 4 ln ⁡ n ) O(\frac{n^{\frac{3}{4}}}{\ln n}) O(lnnn43)

#include
using namespace std;
typedef long long LL;
const int N=1000005;
LL f1[N],f2[N],n,lim;
int main()
{
     
	scanf("%lld",&n);
	lim=sqrt(n);
	for(LL i=1;i<=lim;++i) f1[i]=i-1,f2[i]=n/i-1;
	for(LL p=2;p<=lim;++p) {
     
		if(f1[p]==f1[p-1]) continue;//不是素数,则跳过
		//以下都是按照公式sum[i]-=sum[i/p]-sum[p-1]进行操作
		for(LL i=1;i<=lim/p;++i) f2[i]-=f2[i*p]-f1[p-1];
		for(LL i=lim/p+1;i<=n/p/p&&i<=lim;++i) f2[i]-=f1[n/i/p]-f1[p-1];
		for(LL i=lim;i>=p*p;--i) f1[i]-=f1[i/p]-f1[p-1];
	}
	printf("%lld\n",f2[1]);
    return 0;
}

你已经学会了与素数玩耍,可以来水一道题了:loj6202 叶氏筛法

其实和上一题差不多,只是公式变成了 s u m ( x ) − = ( s u m ( ⌊ i p ⌋ ) − s u m ( p − 1 ) ) × p sum(x)-=(sum(\lfloor \frac{i}{p} \rfloor)-sum(p-1)) \times p sum(x)=(sum(pi)sum(p1))×p

此外,如果你不想写高精的话,最后一个样例是过不去的…

#include
using namespace std;
typedef long long LL;
typedef double db;
const int N=1000005;
const db eps=1e-12;
db f1[N],f2[N];LL l,r;
db sum(LL x) {
     return (db)(x+1)*(db)x/2.0;}
db query(LL x) {
     
	if(x==0) return 0;
	LL lim=sqrt(x);
	for(LL i=1;i<=lim;++i) f1[i]=sum(i),f2[i]=sum(x/i);
	for(LL p=2;p<=lim;++p) {
     
		if(fabs(f1[p]-f1[p-1])<eps) continue;
		db tmp=f1[p-1];
		for(LL i=1;i<=lim/p;++i) f2[i]-=(f2[i*p]-tmp)*(db)p;
		for(LL i=lim/p+1;i<=x/p/p&&i<=lim;++i)
			f2[i]-=(db)(f1[x/i/p]-tmp)*(db)p;
		for(LL i=lim;i>=p*p;--i) f1[i]-=(f1[i/p]-tmp)*(db)p;
	}
	return f2[1];
}
int main()
{
     
	scanf("%lld%lld",&l,&r);
	printf("%.0lf\n",query(r)-query(l-1));
    return 0;
}

与积性函数玩耍

例题:spoj-DIVCNTK

…看代码吧。关键思想是每个f(x)的贡献是在处理x的最大质因子时算上的,x的最大质因子次数是1的时候可以用前缀和快速处理,否则可以枚举处理。

#include
using namespace std;
typedef unsigned long long uLL;
uLL read() {
     
	uLL q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+(uLL)(ch-'0'),ch=getchar();
	return q;
}
const int N=200100;
uLL n,K,lim,tot,T,ans;
uLL f1[N],f2[N],pri[N];
void init(uLL x) {
     //首先筛出素数个数前缀和
	lim=sqrt(x),tot=0;
	if(x<=1) return;//这句话挺重要的
	for(uLL i=1;i<=lim;++i) f1[i]=i-1,f2[i]=x/i-1;
	for(uLL p=1;p<=lim;++p) {
     
		if(f1[p]==f1[p-1]) continue;
		pri[++tot]=p;uLL t=f1[p-1];
		for(uLL i=1;i<=lim/p;++i) f2[i]-=f2[i*p]-t;
		for(uLL i=lim/p+1;i<=x/p/p&&i<=lim;++i) f2[i]-=f1[x/i/p]-t;
		for(uLL i=lim;i>=p*p;--i) f1[i]-=f1[i/p]-t;
	}
}
void work(uLL pos,uLL res,uLL rem) {
     //rem:remain
	uLL t=(rem<=lim?f1[rem]:f2[n/rem]);
	ans+=(K+1)*res*(t-f1[pri[pos-1]]);
	//一举计算多个f(x)的贡献,其中x的最大质因子次数为1
	for(uLL i=pos;i<=tot;++i) {
     
		if(rem<pri[i]*pri[i]) return;
		//若满足这个if条件,说明pri[i]最多作为一个x的最大质因子,且次数只能为1,没有重复计算的必要
		for(uLL now=pri[i],js=1;now<=rem;now*=pri[i],++js) {
     
			if(now*pri[i]<rem) work(i+1,res*(K*js+1),rem/now);
			//如果不加前面的if判断的话,后面计算的(t-f1[pri[pos-1]])可能会变成负数
			if(js>=2) ans+=res*(K*js+1);//最大质因子的次数不为1的x对应的f(x)在此计算
		}
	}
}
int main()
{
     
	T=read();
	while(T--) {
     
		n=read(),K=read();
		ans=1,init(n),work(1,1,n);
		printf("%llu\n",ans);
	}
    return 0;
}

好吧,如果你看懂了上面那道题,那么再来一道?loj6053 简单的函数

这道题和上一道也差不多,就是我们要计算出质数异或1的前缀和。

于是我们可以筛出质数前缀和以及质数个数前缀和。由于质数中偶数只有2,那么只有2异或1后会加1,其他都会减1,这样就能够把质数异或1的前缀和求出来了。

#include
using namespace std;
typedef long long LL;
const LL mod=1000000007,div2=500000004;
const int N=100005;
LL n,lim,tot,ans,f1[N],f2[N],s1[N],s2[N],pri[N];
LL mul(LL a,LL b) {
     //快速乘,防止炸long long
	LL re=0;
	for(;b;b>>=1,a=(a+a)%mod) if(b&1) re=(re+a)%mod;
	return re;
}
LL sum(LL x) {
     return mul(x+1,x)*div2%mod;}
void init() {
     
	if(n<=1) return;
	lim=sqrt(n);LL tf,ts;
	for(LL i=1;i<=lim;++i)
		f1[i]=sum(i)-1,f2[i]=sum(n/i)-1,s1[i]=i-1,s2[i]=n/i-1;
	for(LL p=2;p<=lim;++p) {
     
		if(s1[p]==s1[p-1]) continue;
		pri[++tot]=p,tf=f1[p-1],ts=s1[p-1];
		for(LL i=1;i<=lim/p;++i) {
     
			f2[i]=(f2[i]-(f2[i*p]-tf)*p)%mod;
			s2[i]-=(s2[i*p]-ts)%mod,s2[i]=(s2[i]+mod)%mod;
		}
		for(LL i=lim/p+1;i<=n/p/p&&i<=lim;++i) {
     
			f2[i]=(f2[i]-(f1[n/i/p]-tf)*p)%mod;
			s2[i]-=(s1[n/i/p]-ts)%mod,s2[i]=(s2[i]+mod)%mod;
		}
		for(LL i=lim;i>=p*p;--i) {
     
			f1[i]-=(f1[i/p]-tf)*p%mod,f1[i]=(f1[i]+mod)%mod;
			s1[i]-=(s1[i/p]-ts)%mod,s1[i]=(s1[i]+mod)%mod;
		}
	}
	for(LL i=1;i<=lim;++i) {
     
		f1[i]=(f1[i]-s1[i]+(i>=2LL)*2LL+mod)%mod;
		f2[i]=(f2[i]-s2[i]+(n/i>=2LL)*2LL+mod)%mod;
	}
}
void work(LL pos,LL res,LL rem) {
     
	LL t=(rem<=lim?f1[rem]:f2[n/rem]);
	ans=(ans+res*(t-f1[pri[pos-1]])%mod)%mod;
	for(LL i=pos;i<=tot;++i) {
     
		if(rem<pri[i]*pri[i]) return;
		for(LL t=pri[i],js=1;t<=rem;t*=pri[i],++js) {
     
			if(t*pri[i]<rem) work(i+1,res*(pri[i]^js)%mod,rem/t);
			if(js>=2) ans=(ans+res*(pri[i]^js)%mod)%mod;
		}
	}
}
int main()
{
     
	scanf("%lld",&n);
	ans=1,init(),work(1,1,n);
	printf("%lld\n",ans);
    return 0;
}

习题

bzoj5244

51nod 1847

uoj188

loj572

51nod 1965

51nod 1222

你可能感兴趣的:(数学,min25筛,扩展埃氏筛)