Min_25 筛入门

Min_25 筛

Min_25 筛

其实质为动态规划

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

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

符号约定:

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

  • \(p_k\) 为第 \(k\) 小的质数,约定 \(p_0=1\)

  • \(F(n,k)=\sum_{i=2}^n[p_k\le lf(i)]f(i)\),注意从 \(2\) 开始。

  • \(F_{p}(n)=\sum_{i=2}^n[i\in \mathbb{P}]f(i)\)

几个性质:

  • \(Ans=F(n,1)+f(1)=F(n,1)+1\)
  • \(\forall n\notin \mathbb{P},lf(n)\le \sqrt n\)
  • \(p^2_k\ge n\implies F(n,k)=F_{p}(n)-F_{p}(p_{k-1})\)

然后我们考虑DP转移。


可以枚举 \(lf(i)\)

\[\begin{aligned} F(n,k)&=\sum_{i=2}^n[p_k\le lf(i)]f(i)\\ &=\sum_{i=k}^{p_i\le n}[lf(i)=p_i]f(i)\\ &=\sum_{i\ge k,p_i^2\le n}[lf(i)=p_i]f(i)+\sum_{p_i^2>n,i\ge k}f(p_i)\\ &=\sum_{i\ge k,p_i^2\le n}\sum_{c=1}^{p_i^{c+1}\le n}\left(f(p_i^c)F(\lfloor\frac{n}{p_i^c}\rfloor,i+1)+f(p_i^{c+1})\right)+\sum_{i\ge k}f(p_i)\\ &=\sum_{i\ge k,p_i^2\le n}\sum_{c=1}^{p_i^{c+1}\le n}\left(f(p_i^c)F(\lfloor\frac{n}{p_i^c}\rfloor,i+1)+f(p_i^{c+1})\right)+F_p(n)-F_p(p_{k-1})\\ \end{aligned} \]

一个说明:对于 \(p_i^{c+1}>n\implies p_{i+1}>p_i>\lfloor\frac{n}{p_i^c}\rfloor\),则 \(F(\lfloor\frac{n}{p_i^c}\rfloor,i+1)=0\)。所以不用计算。

递推式的几个性质:

  • 前半部分枚举只需要所有 \(p_i\le \sqrt n\)

  • 后半部分在整个DP过程中只需要用到 \(O(\sqrt n)\) 个,也就是 \(\lfloor\frac{n}{i}\rfloor\) 所提供的 \(O(\sqrt n)\) 以及所有 \(p^2\le n\)\(F_p\)

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

    在这里考虑采用特殊处理 \(F_p(p_{k-1})\),在线性筛的时候直接算即可

根据递推式暴力计算的时间复杂度为 \(O(n^{1-\epsilon})\),zzt 在论文中证明了 \(n\le 10^{13}\) 的时候约等于 \(O(\frac{n^{\frac{3}{4}}}{\log n})\)(这玩意优于杜教和PN 的 \(O(n^{\frac{2}{3}})\)

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


问题规约为求 \(F_p\)

考虑一个东西:

  • \(f(p)\) 是关于 \(p\) 的较低次数多项式,所以可以写为 \(f(p)=\sum a_ip^i\)

    所以我们只需要对每个指数都求一遍即可。也就是跑"次数"次 \(\sum_{p\le n} p^i\)

我们考虑对于每个 \(k\) 进行求解。考虑设 \(g(x)=x^k\),我们的问题变成了对所有的 \(m=\lfloor\frac{n}{i}\rfloor\) 求解 \(G(m)=\sum_{i=2}^m[i\in \mathbb{P}]i^k\),最后将系数乘上,累加进去即可。

我们考虑设 \(G(m,k)=\sum_{i=2}^m[i\in \mathbb{P}| lf(i)> p_k]g(i)\)

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

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

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

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

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

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

  • \(k>0:\)

    考虑 \(p_k^2>m\) 的部分,没有影响,直接继承 \(G(m,k-1)\)

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

    也就是 \(lf\)\(p_k\) 的所有值。考虑容斥。

    所有 \(lf(i)\le p_k\) 的根据完全积性函数可以被表示为 \(g(p_k)G(\lfloor\frac{m}{p_k}\rfloor,k-1)\),意义是确保了原本 \(2\sim \lfloor\frac{m}{p_k}\rfloor\) 的数其 $lf $ 全部大于 \(p_{k-1}\),然后钦定加入一个 \(p_k\) 进去

    但是在这里会发现质数没有考虑到,且被删掉了。所以我们加回来,恰好这个答案是 \(g(p_k)G(p_{k-1},k-1)\)

    所以有:

    \(G(m,k)=G(m,k-1)-[p_k^2\le n](g(p_k)G(\lfloor\frac{m}{p_k}\rfloor,k-1)-g(p_k)G(p_{k-1},k-1))\)

并且根据数论分块的知识,\(\lfloor\frac{m}{p_k}\rfloor\) 也在需要计算的值里。

总复杂度可以估计为:

\[O(\sum_{i=1}^{\sqrt n}\pi(\sqrt i))=O(\frac{n^{\frac{3}{4}}}{\ln n}) \]

常数很小。

只能说 NB!


例题:

梦中的数论

求解:

\[\begin{aligned} \sum_{i=1}^{n}\sum_{j=1}^n\sum_{k=1}^n[(j|i)][(j+k)|i] \end{aligned} \]

考虑化简:

\[\begin{aligned} \sum_{i=1}^{n}\sum_{j=1}^n\sum_{k=1}^n[(j|i)][(j+k)|i]&=\sum_{i=1}^n\sum_{j|i}\sum_{k|i}[j<k]\\ &=\frac{\sum_{i=1}^nd^2(i)-d(i)}{2} \end{aligned} \]

\(d(i)\) 是约数个数。

我们知道 \(\sum_{i=1}^nd(i)=\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor\)

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

带入Min_25筛模型,有 \(f(p)=4,f(p^c)=(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妙啊。

一个优化常数的技巧,在 \(n\ge 10^{10}\) 后有大用。

\(\lfloor\frac{n}{i}\rfloor\) 的值按照 \(\le sqrt n\)\(>\sqrt n\) 分开存在 \([1,\sqrt n],[\sqrt n+5,2\sqrt n+5]\) 的下标里,避免 unordered_map 的常数开销

posted @ 2024-07-19 12:20  spdarkle  阅读(5)  评论(0编辑  收藏  举报