Min-25筛超实用型模板

本文介绍了Min-25筛的原理、适用条件及其在积性函数求和中的应用。通过相关链接提供了学习资源,并强调Min-25筛在满足特定条件的函数中能实现低于线性的复杂度。文章还包含模板代码示例及解题思路,例如用于求质数前缀和的问题。

前言

原本以为会了杜教筛就能走遍积性函数题的天下都不怕的了,结果5月省赛时遇到了专门卡杜教筛的题,只好滚回去学了…结果省赛之后一直在忙这忙那的,然后一拖拖到了暑假的现在才有时间来补Min-25筛的内容。

复杂度 O ( n 3 4 log ⁡ n ) O(\frac{n^\frac{3}{4}}{\log n}) O(lognn43),而且据说常数非常优秀
证明?那是什么,能吃吗?

相关链接

Min-25筛原理:
Min25筛小结
min25筛学习笔记
【学习笔记】Min25筛

模板改编自:
LibreOJ 6053 简单的函数(Min25筛)
min25筛学习笔记

注意

正如在上面的链接中所提到的,Min-25筛使用条件是:

  1. f ( x ) f(x) f(x) x ∈ p r i m e x \in prime xprime 的时候要能够用多项式表示,如 f ( p ) = p 2 + p f(p)=p^2+p f(p)=p2+p
  2. f ( x k ) f(x^k) f(xk) x ∈ p r i m e x \in prime xprime 时要能够快速算出

关于第一点,之所以要求其能用多项式表示,是为了能把其写成多个完全积性函数的若干倍的和/差,这样在第一部分 P j 2 ≤ n P_j^2 \leq n Pj2n 时才可以把 f ( P j ) f(P_j) f(Pj) 直接提取出来。

关于第二点,则是为了能在第二部分中以 O ( 1 ) O(1) O(1) 复杂度算出 f ( x k ) , x ∈ p r i m e f(x^k),x\in prime f(xk),xprime,否则复杂度将变大。

故Min-25筛并不只能作用于积性函数,只要函数 f f f 满足以上两个条件,就可以使用Min-25筛在低于线性的复杂度下求其前缀和。(比如2020年CCPC网络赛中有一题就是用Min-25筛来求质数的前缀和)

在第一部分求多项式的不同次项时,只会求 g ( ⌊ n i ⌋ ) g(\lfloor \frac{n}{i} \rfloor) g(in),因为只有这些值会被用到。虽然这样子能节省一定的时间,但是也因为不同的 n n n 所对应的 ⌊ n i ⌋ \lfloor \frac{n}{i} \rfloor in 列表往往很大不同,导致了多组数据时已算出来的结果不能复用,所以每输入一组数据就得重新做一次。

代码

代码的 f f f 函数和 s i e v e _ g sieve\_g sieve_g 函数中的九处 //...是需要根据具体题目要求进行填空的。

//代码计算的是函数f(i)的前n项和
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 2e5 + 10;//2*sqrt(n)的范围
const LL INV2 = 5e8 +4;//2的逆元(可能用到)
const LL INV6 = 166666668;//6的逆元(可能用到)
const LL MOD = 1e9 + 7;

bool isp[N];
LL n,m,sz,sqrt_n,c0,c1,p[N],w[N],id0[N],id1[N],g0[N],g1[N],sum0[N],sum1[N];
//n是输入的数, sqrt_n保存sqrt(n),即预处理的数量
//sz是质数个数, isp[i]表示i是否质数, p[i]存储第i个质数
//sum0[i]存储的是前i个质数的f0值之和,sum1[i]存储的是前i个质数的f1值之和
//m是n/k的个数, w[i]存储n/k第i种值(倒序), id0和id1[i]存储i这个值在w[i]中的下标
//g0[i]和g1[i]等分别存储f在取质数时的多项式中的不同次方项(此处只有两个数组,即假设题目中的f取质数时只有两项), g0[i]存的是g(w[i],0~sz), g1[i]存的是g1(w[i],0~sz)
//g0(n,i) = \Sigma_{j=1}^{n}[j是质数 or j的最小质因子>p[i]]*f0(j) 其中f0为f取质数时的第一个次方项
//g1(n,i) = \Sigma_{j=1}^{n}[j是质数 or j的最小质因子>p[i]]*f1(j) 其中f1为f取质数时的第二个次方项
//c0和c1等保存的是不同次方项的系数(此处只有两个系数,即假设题目中的f取质数时只有两项)

//计算f(p^t),若要降低常数也可把这个函数用增量法在调用处实现
inline LL f(LL p,LL t)
{
	//...
}

//线性筛,求函数f0、f1在前i个质数处的前缀和
void init(LL n)
{
    sz=0;
    memset(isp,1,sizeof(isp));
    isp[1]=0;
    sum0[0]=0;
    sum1[0]=0;
    for (LL i=2; i<=n; i++)
    {
        if (isp[i])
        {
            p[++sz]=i;
            //计算sum0,即sum0(i) = \Sigma_{j=1}^{i}f0(p[j])
            //...
            //计算sum1,即sum1(i) = \Sigma_{j=1}^{i}f1(p[j])
            //...
        }
        for (int j=1; j<=sz&&p[j]*i<=n; j++)
        {
            isp[i*p[j]]=0;
            if (i%p[j]==0) break;
        }
    }
}

inline int get_id(LL x) {
    if(x<=sqrt_n) return id0[x];
    else return id1[n/x];
}

//计算原理中的多项式的项, 只会计算g0(n/i), g1(n/i)
void sieve_g(LL n)
{
    m=0;
    for (LL i=1,j;i<=n;i=j+1)
    {
        LL k=n/i; j=n/k;
        w[++m]=k;
        if(k<=sqrt_n) id0[k]=m;
    	else id1[n/k]=m;

        k%=MOD;
        //计算原理中的g0(w[m],0),即\Sigma_{j=2}^{w[m]}f0(j), 存在g0[m]中
        //...
        //计算原理中的g1(w[m],0),即\Sigma_{j=2}^{w[m]}f1(j), 存在g1[m]中
        //...
    }
    for (int i=1;i<=sz;i++)
        for (int j=1;j<=m&&p[i]*p[i]<=w[j];j++)
        {
        	int op=get_id(w[j]/p[i]);
            //根据g0[j]=(g0[j]-f0(p[i])*((g0[op]-sum0[i-1]+MOD)%MOD)%MOD+MOD)%MOD计算
            //...
            //根据g1[j]=(g1[j]-f1(p[i])*((g1[op]-sum1[i-1]+MOD)%MOD)%MOD+MOD)%MOD计算
            //...
        }
}

LL S(LL x,LL y)
{
    if (x<=1||p[y]>x) return 0;//base case
    LL k=get_id(x),res=0;
    res=((c0*g0[k]%MOD+c1*g1[k]%MOD+MOD)%MOD-(c0*sum0[y-1]%MOD+c1*sum1[y-1]%MOD+MOD)%MOD+MOD)%MOD;//质数部分的贡献
    //下面的二重循环统计的是合数部分的贡献
    for(int i=y;i<=sz&&p[i]*p[i]<=x;i++)//枚举合数的最小质因子
    {
        LL t0=p[i], t1=p[i]*p[i];
        for(LL e=1; t1<=x; t0=t1,t1*=p[i],e++)//枚举最小质因子的次数
        {
            LL fp0=f(p[i],e), fp1=f(p[i],e+1);
            (res+=(fp0*S(x/t0,i+1)%MOD+fp1)%MOD)%=MOD;
        }
    }
    return res;
}

int main()
{
    //freopen("test.in","r",stdin);
	scanf("%lld",&n);
    sqrt_n=sqrt(n);
    init(sqrt_n); sieve_g(n);
    //此处对不同次项的系数c0,c1进行直接赋值
    //...
    //此处计算的是原函数f在取值为1时的函数值,即f(1),存在f_1中;若是积性函数的话一般有f(1)=1
    //...
    printf("%lld\n",((S(n,1)+f_1)%MOD));
    return 0;
}

例题

P5325 【模板】Min_25筛

可以看到, f f f 在取质数值时有 f ( p ) = p 2 − p f(p)=p^2-p f(p)=p2p,故可以提取出来 f 0 ( p ) = p 2 , f 1 ( p ) = p , c 0 = 1 , c 1 = − 1 f_0(p)=p^2, f_1(p)=p, c_0=1, c_1=-1 f0(p)=p2,f1(p)=p,c0=1,c1=1,使得 f ( p ) = c 0 ∗ f 0 ( p ) + c 1 ∗ f 1 ( p ) f(p)=c_0*f_0(p)+c_1*f_1(p) f(p)=c0f0(p)+c1f1(p)

同时, f f f 在取质数的次方时有 f ( p t ) = p t ( p t − 1 ) f(p^t)=p^t(p^t-1) f(pt)=pt(pt1),这可以通过快速幂快速求出,或者直接在S函数中通过增量法更快地求出。

于是可以在此分析的基础上对上述模板进行代码填空:

//代码计算的是函数f(i)的前n项和
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 2e5 + 10;//2*sqrt(n)的范围
const LL INV2 = 5e8 +4;//2的逆元(可能用到)
const LL INV6 = 166666668;//6的逆元(可能用到)
const LL MOD = 1e9 + 7;

bool isp[N];
LL n,m,sz,sqrt_n,c0,c1,p[N],w[N],id0[N],id1[N],g0[N],g1[N],sum0[N],sum1[N];
//n是输入的数, sqrt_n保存sqrt(n),即预处理的数量
//sz是质数个数, isp[i]表示i是否质数, p[i]存储第i个质数
//sum0[i]存储的是前i个质数的f0值之和,sum1[i]存储的是前i个质数的f1值之和
//m是n/k的个数, w[i]存储n/k第i种值(倒序), id0和id1[i]存储i这个值在w[i]中的下标
//g0[i]和g1[i]等分别存储f在取质数时的多项式中的不同次方项(此处只有两个数组,即假设题目中的f取质数时只有两项), g0[i]存的是g(w[i],0~sz), g1[i]存的是g1(w[i],0~sz)
//g0(n,i) = \Sigma_{j=1}^{n}[j是质数 or j的最小质因子>p[i]]*f0(j) 其中f0为f取质数时的第一个次方项
//g1(n,i) = \Sigma_{j=1}^{n}[j是质数 or j的最小质因子>p[i]]*f1(j) 其中f1为f取质数时的第二个次方项
//c0和c1等保存的是不同次方项的系数(此处只有两个系数,即假设题目中的f取质数时只有两项)

//计算f(p^t),若要降低常数也可把这个函数用增量法在调用处实现
inline LL f(LL p,LL t)
{
	LL tmp=p%MOD, ret=1;
    while (t>0)
    {
        if (t&1) (ret*=tmp)%=MOD;
        (tmp*=tmp)%=MOD;
        t>>=1;
    }
    (ret*=((ret-1+MOD)%MOD))%=MOD;
    return ret;
}

//线性筛,求函数f0、f1在前i个质数处的前缀和
void init(LL n)
{
    sz=0;
    memset(isp,1,sizeof(isp));
    isp[1]=0;
    sum0[0]=0;
    sum1[0]=0;
    for (LL i=2; i<=n; i++)
    {
        if (isp[i])
        {
            p[++sz]=i;
            //计算sum0,即sum0(i) = \Sigma_{j=1}^{i}f0(p[j])
            sum0[sz]=(sum0[sz-1]+i*i%MOD)%MOD;
            //计算sum1,即sum1(i) = \Sigma_{j=1}^{i}f1(p[j])
            sum1[sz]=(sum1[sz-1]+i)%MOD;
        }
        for (int j=1; j<=sz&&p[j]*i<=n; j++)
        {
            isp[i*p[j]]=0;
            if (i%p[j]==0) break;
        }
    }
}

inline int get_id(LL x) {
    if(x<=sqrt_n) return id0[x];
    else return id1[n/x];
}

//计算原理中的多项式的项, 只会计算g0(n/i), g1(n/i)
void sieve_g(LL n)
{
    m=0;
    for (LL i=1,j;i<=n;i=j+1)
    {
        LL k=n/i; j=n/k;
        w[++m]=k;
        if(k<=sqrt_n) id0[k]=m;
    	else id1[n/k]=m;

        k%=MOD;
        //计算原理中的g0(w[m],0),即\Sigma_{j=2}^{w[m]}f0(j), 存在g0[m]中
        g0[m]=k*(k+1)%MOD*(2*k+1)%MOD*INV6%MOD;
        g0[m]=(g0[m]-1+MOD)%MOD;
        //计算原理中的g1(w[m],0),即\Sigma_{j=2}^{w[m]}f1(j), 存在g1[m]中
        g1[m]=k*(k+1)/2%MOD;
        g1[m]=(g1[m]-1+MOD)%MOD;
    }
    for (int i=1;i<=sz;i++)
        for (int j=1;j<=m&&p[i]*p[i]<=w[j];j++)
        {
        	int op=get_id(w[j]/p[i]);
            //根据g0[j]=(g0[j]-f0(p[i])*((g0[op]-sum0[i-1]+MOD)%MOD)%MOD+MOD)%MOD计算
            g0[j]=(g0[j]-p[i]*p[i]%MOD*((g0[op]-sum0[i-1]+MOD)%MOD)%MOD+MOD)%MOD;
            //根据g1[j]=(g1[j]-f1(p[i])*((g1[op]-sum1[i-1]+MOD)%MOD)%MOD+MOD)%MOD计算
            g1[j]=(g1[j]-p[i]*((g1[op]-sum1[i-1]+MOD)%MOD)%MOD+MOD)%MOD;
        }
}

LL S(LL x,LL y)
{
    if (x<=1||p[y]>x) return 0;//base case
    LL k=get_id(x),res=0;
    res=((c0*g0[k]%MOD+c1*g1[k]%MOD+MOD)%MOD-(c0*sum0[y-1]%MOD+c1*sum1[y-1]%MOD+MOD)%MOD+MOD)%MOD;//质数部分的贡献
    //下面的二重循环统计的是合数部分的贡献
    for(int i=y;i<=sz&&p[i]*p[i]<=x;i++)//枚举合数的最小质因子
    {
        LL t0=p[i], t1=p[i]*p[i];
        for(LL e=1; t1<=x; t0=t1,t1*=p[i],e++)//枚举最小质因子的次数
        {
            LL fp0=f(p[i],e), fp1=f(p[i],e+1);
            (res+=(fp0*S(x/t0,i+1)%MOD+fp1)%MOD)%=MOD;
        }
    }
    return res;
}

int main()
{
    //freopen("test.in","r",stdin);
	scanf("%lld",&n);
    sqrt_n=sqrt(n);
    init(sqrt_n); sieve_g(n);
    //此处对不同次项的系数c0,c1进行直接赋值
    c0=1, c1=-1;
    //此处计算的是原函数f在取值为1时的函数值,即f(1),存在f_1中;若是积性函数的话一般有f(1)=1
    LL f_1=1;
    printf("%lld\n",((S(n,1)+f_1)%MOD));
    return 0;
}
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值