Min_25 筛入门

Min_25 筛

Min_25 筛

其实质为动态规划

只能用于求积性函数前缀和。

要求积性函数 f 满足 f(p) 是一个关于 p 的较低次数多项式

符号约定:

  • lf(n)n 的最小质因子,

  • pk 为第 k 小的质数,约定 p0=1

  • F(n,k)=i=2n[pklf(i)]f(i),注意从 2 开始。

  • Fp(n)=i=2n[iP]f(i)

几个性质:

  • Ans=F(n,1)+f(1)=F(n,1)+1
  • nP,lf(n)n
  • pk2nF(n,k)=Fp(n)Fp(pk1)

然后我们考虑DP转移。


可以枚举 lf(i)

F(n,k)=i=2n[pklf(i)]f(i)=i=kpin[lf(i)=pi]f(i)=ik,pi2n[lf(i)=pi]f(i)+pi2>n,ikf(pi)=ik,pi2nc=1pic+1n(f(pic)F(npic,i+1)+f(pic+1))+ikf(pi)=ik,pi2nc=1pic+1n(f(pic)F(npic,i+1)+f(pic+1))+Fp(n)Fp(pk1)

一个说明:对于 pic+1>npi+1>pi>npic,则 F(npic,i+1)=0。所以不用计算。

递推式的几个性质:

  • 前半部分枚举只需要所有 pin

  • 后半部分在整个DP过程中只需要用到 O(n) 个,也就是 ni 所提供的 O(n) 以及所有 p2nFp

    注意到递归的时候都保证了 pi2n,所以传进去的 i+1 至多是 O(n)​​ 。注意边界

    在这里考虑采用特殊处理 Fp(pk1),在线性筛的时候直接算即可

根据递推式暴力计算的时间复杂度为 O(n1ϵ),zzt 在论文中证明了 n1013 的时候约等于 O(n34logn)(这玩意优于杜教和PN 的 O(n23)

所以一般直接暴力算是可过的


问题规约为求 Fp

考虑一个东西:

  • f(p) 是关于 p 的较低次数多项式,所以可以写为 f(p)=aipi

    所以我们只需要对每个指数都求一遍即可。也就是跑"次数"次 pnpi

我们考虑对于每个 k 进行求解。考虑设 g(x)=xk,我们的问题变成了对所有的 m=ni 求解 G(m)=i=2m[iP]ik,最后将系数乘上,累加进去即可。

我们考虑设 G(m,k)=i=2m[iP|lf(i)>pk]g(i)

这东西相当于埃氏筛法在筛除第 k 个质数之后尚未被筛的所有数的 g 之和

根据 lf 的性质,对于所有的合数 xm,lf(x)m

所以我们只需要算到 G(m,m) 就可以求得相应的 G(m) 了。

这样拆分系数的关键是 g 是完全积性函数,这是不带 ai 的根由。

所以我们考虑埃氏筛法的过程DP G(m,k)

  • k=0:G(m,k)=i=2mik 可以用等幂和公式直接计算,注意不包含零

  • k>0:

    考虑 pk2>m 的部分,没有影响,直接继承 G(m,k1)

    否则考虑直接继承之后会少掉什么。

    也就是 lfpk 的所有值。考虑容斥。

    所有 lf(i)pk 的根据完全积性函数可以被表示为 g(pk)G(mpk,k1),意义是确保了原本 2mpk 的数其 lf 全部大于 pk1,然后钦定加入一个 pk 进去

    但是在这里会发现质数没有考虑到,且被删掉了。所以我们加回来,恰好这个答案是 g(pk)G(pk1,k1)

    所以有:

    G(m,k)=G(m,k1)[pk2n](g(pk)G(mpk,k1)g(pk)G(pk1,k1))

并且根据数论分块的知识,mpk 也在需要计算的值里。

总复杂度可以估计为:

O(i=1nπ(i))=O(n34lnn)

常数很小。

只能说 NB!


例题:

梦中的数论

求解:

i=1nj=1nk=1n[(j|i)][(j+k)|i]

考虑化简:

i=1nj=1nk=1n[(j|i)][(j+k)|i]=i=1nj|ik|i[j<k]=i=1nd2(i)d(i)2

d(i) 是约数个数。

我们知道 i=1nd(i)=i=1nni

所以问题在于求解约数个数的平方和。

带入Min_25筛模型,有 f(p)=4,f(pc)=(c+1)4

进而可以得到 g(p)=1,系数为 4。做一遍Min_25筛即可。

主要是提供一份模版。

注意这玩意甚至不需要记忆化。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define N 1050500
const int mod=998244353;
unordered_map<int,int>fp;
int v[N],tot,p[N],sp[N],n,m;
int f(int p,int c){
	return (c+1)*(c+1)%mod;
}
int calcf(int n,int k){
	int res=fp[n]-sp[k-1];
	for(int i=k;i<=tot;++i){
		if(p[i]*p[i]>n)break;
		int now=p[i];
		for(int c=1;now*p[i]<=n;++c,now*=p[i]){
			res+=f(p[i],c)*calcf(n/now,i+1)%mod+f(p[i],c+1);res%=mod;
		}
	}
	return res;
}
/*
这个题里面f(p)=4,因为是约数个数的平方
*/
void init(int n){//sqrt n
	vector<int>ud;
	for(int l=1,r;l<=n;l=r+1){
		r=min(n/(n/l),n);
		ud.push_back(n/l);
	}
	//这玩意可以直接滚动数组,每次找最前面的
    /*从大到小存储,每次直接算,如果不必再算就pop*/
	int t=sqrt(n)+1;
	for(auto x:ud)fp[x]=(x-1)%mod;
	for(int i=2;i<=t;++i){
		if(!v[i]){
			v[i]=1;p[++tot]=i;sp[tot]=sp[tot-1]+1,sp[tot]%=mod;
			for(int j=i;j<=t;j+=i)v[j]=1;
			if(ud.empty())continue;
			for(auto x:ud){
				if(i*i<=x)fp[x]-=fp[x/i]-sp[tot-1],fp[x]%=mod;
			}
			while(!ud.empty()&&i*i>ud[ud.size()-1]){
				ud.pop_back();
			}
		}
	}
	for(int i=1;i<=tot;i++)sp[i]*=4,sp[i]%=mod;/*算上系数*/
	for(int l=1,r;l<=n;l=r+1){
		r=min(n/(n/l),n);fp[n/l]*=4,fp[n/l]%=mod;
	}
}
signed main(){
    cin>>n;
	init(n);
	int res=calcf(n,1)+1;
	for(int l=1,r;l<=n;l=r+1){
		r=min(n/(n/l),n);
		res-=(r-l+1)*(n/l)%mod;
	}
	res%=mod;
	res*=((mod+1)>>1);
	res=(res%mod+mod)%mod;
	cout<<res<<"\n";
}

只能说这个DP妙啊。

一个优化常数的技巧,在 n1010 后有大用。

ni 的值按照 sqrtn>n 分开存在 [1,n],[n+5,2n+5] 的下标里,避免 unordered_map 的常数开销

posted @   spdarkle  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示