专栏文章

浅谈一种更方便的的线性解积性函数方式

算法·理论参与者 4已保存评论 5

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
5 条
当前快照
1 份
快照标识符
@mj5z5gig
此快照首次捕获于
2025/12/15 01:04
2 个月前
此快照最后确认于
2026/02/16 01:30
4 天前
查看原文

I 前言&引入

在此文章中,我们假设您已经彻底理解欧拉筛的原理,明白积性函数的定义,会使用欧拉筛计算欧拉函数等基础应用
让我们先看一道例题:
例题
定义集合 P\mathbb{P} 为质数集。
定义函数 rk(x),xP\operatorname{rk}(x),x\in \mathbb{P} 表示 xx 是第 rk(x)\operatorname{rk}(x) 个质数,如 rk(2)=1,rk(3)=2,rk(5)=3\operatorname{rk}(2)=1,\operatorname{rk}(3)=2,\operatorname{rk}(5)=3
定义积性函数 f(x)\operatorname{f}(x)pk,pP,nNp^k,p\in \mathbb{P},n\in \mathbb{N} 处取值为 krk(p)+1k\cdot \operatorname{rk}(p)+1
QQ 次询问,每次询问一个 f(x)\operatorname{f}(x)
1Q,x1071\le Q,x\le 10^7
(不考虑读入读出)
首先,先跑一遍欧拉筛,把 rk(x)\operatorname{rk}(x) 预处理出来。
我们发现,在预处理 f(x)\operatorname{f}(x) 时,若使用欧拉筛,那么在欧拉筛板子对应高亮部分难以处理。
CPP
for(int i=2;i<=n;++i)
{
	if(!is_np[i])
	{
		Prime[++cnt]=i;
	}
	for(int j=1;1ll*ps[j]*i<=n;++j)
	{
		is_np[ps[j]*i]=1;
		if(i%ps[j]==0)
		{
			break;
		}
	}
}
当然,使用欧拉筛肯定是可行的,只要开一个记录被筛的最小质因子,和最小质因子的次数,将其除去倒找也可,但是对于状态复杂的积性函数求解问题而言,终究思路略显繁杂。
于是,本文的主角——明灯筛(Sieve of Tomo in Row,RR)法就krkrdkdk的登场了。

II 明灯筛

我们先来回顾一下欧拉筛。
欧拉筛的本质是什么?让每个数的最小质因子把它筛去。这样每个数只会被筛一次,时间复杂度达到 O(n)\operatorname{O}(n)
但是,欧拉筛并不完全适配积性函数的求解问题。当数字的最小质因子不止为一次时,就要重新设定转移方程,乃至通过上文的方法“溯源”,在思维量上略大。
有没有不耗脑子的方法呢?我们稍稍改变策略,让一个数的所有最小质因子之积把它筛去。什么意思呢?如果我们将正在被筛的数 nn 质因数分解为 p1k1p2k2pnkn,p1<p2<<pnp_1^{k_1}p_2^{k_2}\cdots p_n^{k_n},p_1<p_2<\cdots<p_n,那么将它筛去的就是 p1k1p_1^{k_1}
容易发现,每个数只会被 p1k1p_1^{k_1} 这一个数筛去,因此这样做的时间复杂度为 O(n)\operatorname{O}(n)
对于这种筛法,我们惊讶地发现,使用它求解积性函数时,只需要处理好所有质数的整次幂,就可以在转移过程中直接相乘了!这样,我们就可以使用较少的思维量解决问题了。

III 关于代码

先放代码再说,此处使用线性筛模版题
CPP
#include <cstdio>
#include <bitset>
using namespace std;
typedef long long ll;
constexpr int maxn=1e8+10;
bitset<maxn> bs;
int Prime[(int)(2e7+10)];
int cnt,n,q;
ll i,j,cg,k;
int main()
{
	bs[0]=bs[1]=1;
	cnt=0;
	scanf("%d %d",&n,&q);
	for(i=2;i<=n;++i)
	{
		if(!bs[i])
		{
			Prime[++cnt]=i;
			for(k=i*i;k<=n;k*=i)
			{
				bs[k]=1;
			}
		}
		for(j=1;j<=cnt&&i%Prime[j]!=0;++j)
		{
			cg=Prime[j];
			while(cg*i<=n)
			{
				bs[cg*i]=1;
				cg*=Prime[j];
			}
			if(cg==Prime[j])
			{
				break;
			}
		}
	}
	while(q--)
	{
		scanf("%lld",&k);
		printf("%d\n",Prime[k]);
	}
	return 0;
}
注意:在处理次幂时,不可以每次单独计算,应当在递推过程中逐步计算,否则时间复杂度会不正确。

IV 后记

本文使用 2020 分钟写完,可能有误,可以在评论指出。
明灯筛是我在上一节无聊的物理课(就那一节无聊,平常都很有意思的)时偷摸(tomo)地想出来的,可能已经有前人发明。如果有,请私信我,我会在文中补充。
谢谢阅读喵。

评论

5 条评论,欢迎与作者交流。

正在加载评论...