min_25 筛
oisdoaiu
·
·
个人记录
大概只会用到素数个数
作用
假设有一个积性函数 f(x),满足 f(p^k) 是关于 p 的多项式。
则 min_25 可以用来求 f 的前缀和。(单点求值)
过程
min_25 主要思想是把质数和合数分开考虑,对于合数考虑枚举最小质因子进行递归处理。
显然 S(n) 可以分为质数的 f 和合数的 f 加起来,分为两个部分处理。
第一部分
令 p_i 表示从小到大第 i 个质数,minp(x) 表示 x 的最小质因子。
由于 f(p) 是一个多项式,我们考虑将每一项拿出来单独处理。(比如说 f(p)=p+p^2,就分别计算 p 和 p^2 的和再加起来)
所以这里考虑一般情况,设 h(x)=x^k,显然 h 是一个完全积性函数。
设一个 dp。
g(n,i)=\sum_{x\le n}h(x)[(x\ is\ prime)\ or\ (minp(x)>p_i)]
那么 g(n,\infty) 就表示 n 以内所有质数对 f 的贡献。
实际上第二维只需要处理到 \le \sqrt n 的质数,因为如果是合数就至少包含两个质因子,所以小的那个肯定小于根号。
考虑从 g(n,i-1) 推到 g(n,i),减少的是那些最小质因子为 p_i 的合数,也就是:
g(n,i) = g(n,i-1)-\sum_{x\le n}[(x\ is\ not\ prime)\ and\ (minp(x)=p_i)]
但是 minp(x)=p_i 的限制不是很好弄,这里 h 是完全积性函数的好处就体现出来了,h(x)=h(\frac x{p_i})\cdot h(p_i)。
所以我们考虑将 x 除掉一个 p_i,将限制变为 minp(\frac x{p_i})\ge p_i,然后可以直接乘上 h(p_i)。
g(n,i) = g(n,i-1)-h(p_i)\cdot\sum_{x\le\lfloor\frac n{p_i}\rfloor}[minp(x)\ge p_i]
假设 $sp(x)$ 表示 $x$ 以内所有质数的 $h$ 的和。
则 $g(n,i)=g(n,i-1)-h(p_i)\cdot(g(\lfloor\frac n{p_i}\rfloor,i-1)-sp(p_{i-1}))
初始化也就是 g(n,0),相当于就是 \sum_{i=1}^nh(i)。(可以理解为 p_0=1)
显然会进行 O(\text{多项式次数}) 次第一部分,为了方便后面描述,令 H(n) 表示 n 以内所有质数的 f 的和。
实际上根据过程,我们可以得到所有 H(\lfloor\frac nx\rfloor) 的值。
第二部分
类似于第一部分,设一个dp:
S(n,i)=\sum_{x\le n}f(x)[(x\ is\ prime)\ or\ (minp(x)\ge p_i)]
注意区别,第一部分是对 h 求和,这里是对 f 求和,而 f 只是积性函数,并不是完全积性函数,所以不能套用做法
为了方便,我们令 p_0=1,那答案就是 S(n,0)。
考虑如何求 S(n,i),分为质数和合数两部分,对于合数部分枚举最小质因子以及次数。
S(n,i)=H(n)+\sum_{p_j>p_i,k}f({p_j}^k)\cdot( S(\lfloor\frac n{p_j}\rfloor,k)+[k>1])
后面那个 [k>1] 是因为 f(p^k) 也要做出贡献,而 p^k 除掉 p^k 只有 1 了,但是 S(\lfloor\frac n{p_j}\rfloor,k) 里面没有包含 1,而 >1 是因为 p^1 贡献放在质数里算了。
## 一些细节
对于第一部分,注意到我们只需要 $\lfloor\frac ni\rfloor$ 处的 $dp$ 值,所以只用存根号个状态,具体来讲就是要重分配标号,对于小于根号的标号就记录到 $id1[x]$,大于根号的记到 $id2[n/x]$ 里面。
`inline int getpos(ll x){return x<=B?id1[x]:id2[n/x];}`
我的写法是没有计算 $1$ 的贡献的,所以最后要加上。
## 想法
注意到 $f(p)$ 只要是能拆成能快速求前缀和的若干完全积性函数 $h(p)$ 就能使用 min_25,所以可能能维护矩阵(?
## 模板
[题目](https://www.luogu.com.cn/problem/P5325)
```cpp
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void Read(T &n){
char ch; bool flag=false;
while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
if(flag)n=-n;
}
typedef long long ll;
const int MOD = 1000000007;
const int inv6 = (MOD+1)/6;
const int inv2 = (MOD+1)/2;
enum{
MAXN = 2000005
};
inline int inc(int a, int b){a+=b;if(a>=MOD)a-=MOD;return a;}
inline int dec(int a, int b){a-=b;if(a<0)a+=MOD;return a;}
inline void iinc(int &a, int b){a=inc(a,b);}
inline void ddec(int &a, int b){a=dec(a,b);}
inline void upd(int &a, ll b){a=(a+b)%MOD;}
ll n;
int B, id1[MAXN], id2[MAXN], num;
ll val[MAXN];
inline int getpos(ll x){return x<=B?id1[x]:id2[n/x];}
int prime[MAXN], pnum, s1[MAXN], s2[MAXN];
char check[MAXN];
inline void Sieve(int n){
check[1] = true;
for(int i=2; i<=n; i++){
if(!check[i]) prime[++pnum] = i, s1[i] = i, s2[i] = 1ll*i*i%MOD;
for(int j=1; j<=pnum and i*prime[j]<=n; j++){
check[i*prime[j]] = true;
if(i%prime[j]==0) break;
}
}
for(int i=1; i<=n; i++) iinc(s1[i],s1[i-1]), iinc(s2[i],s2[i-1]);
}
inline int sx2(ll n){n%=MOD;return n*(n+1)%MOD*(2*n+1)%MOD*inv6%MOD;}
inline int sx(ll n){n%=MOD;return n*(n+1)%MOD*inv2%MOD;}
int g1[MAXN], g2[MAXN];
int solve(int idn, int idp){
// printf("solve %d %d\n",val[idn],prime[idp]);
int P = prime[idp]; ll n = val[idn];
if(P>=n) return 0;
int res=dec(dec(g2[idn],g1[idn]),dec(s2[P],s1[P]));
for(int i=idp+1; i<=pnum and 1ll*prime[i]*prime[i]<=n; i++){
int p = prime[i], sqr = 1ll*p*p%MOD, t1 = sqr, t2 = p;
ll cur = p;
while(n/p >= cur){
upd(res,1ll*dec(t1,t2)*solve(getpos(n/cur),i));
t1 = 1ll*sqr*t1%MOD, t2 = 1ll*p*t2%MOD, cur *= p;
iinc(res,dec(t1,t2));
}
}
return res;
}
int main(){
Read(n); B = sqrt(n);
Sieve(B);
for(ll l=1; l<=n; l=n/(n/l)+1){
ll x = n/l;
val[++num] = x;
if(x<=B) id1[x] = num; else id2[n/x] = num;
}
for(int i=1; i<=num; i++) g1[i] = dec(sx(val[i]),1), g2[i] = dec(sx2(val[i]),1);
for(int i=1; i<=pnum; i++) for(int j=1; j<=num and 1ll*prime[i]*prime[i]<=val[j]; j++)
ddec(g2[j],1ll*prime[i]*prime[i]%MOD*dec(g2[getpos(val[j]/prime[i])],s2[prime[i-1]])%MOD),
ddec(g1[j],1ll*prime[i]*dec(g1[getpos(val[j]/prime[i])],s1[prime[i-1]])%MOD);
prime[0] = 1;
cout<<inc(solve(1,0),1)<<'\n';
return 0;
}
```