loj 6053 简单的函数(min_25筛模板)

在这里插入图片描述


25筛模板题
使用min_25的三个条件:
1. f ( x ) f(x) f(x) 是积性函数
2. f ( p ) f(p) f(p) 是一个简单多项式,p是素数
3. f ( p x ) f(p^x) f(px) 容易求得(指 O ( 1 ) O(1) O(1)时间内求得),p是素数
min_25筛用来求一些积性函数的前缀和 ∑ i = 1 n f ( i ) \sum_{i = 1}^nf(i) i=1nf(i)

其大致思想是:将答案拆成质数和合数部分: ∑ i = 1 ∣ P ∣ f ( p i ) + ∑ i = 1 n f ( i ) [ i ! = 1 , i ∉ P ] \sum_{i = 1}^{|P|}f(p_i) + \sum_{i = 1} ^ nf(i)[i != 1 ,i \notin P] i=1Pf(pi)+i=1nf(i)[i!=1,i/P]
如何求质数部分的答案:维护 g ( n , j ) g(n,j) g(n,j)表示 ∑ i = 1 n f ( i ) [ i 是 素 数 或 i 的 最 小 素 因 子 大 于 p j ] \sum_{i = 1} ^ nf(i)[i 是素数 或i的最小素因子大于 p_j] i=1nf(i)[iipj],转移: g ( n , j ) = g ( n , j − 1 ) − f ( p j ) ∗ ( g ( ⌊ n p j ⌋ , j − 1 ) − ∑ i = 1 j − 1 f ( p i ) ) g(n,j) = g(n,j - 1) - f(p_j)*(g(\lfloor\frac{n}{p_j}\rfloor,j - 1) - \sum_{i = 1}^{j - 1} f(p_i)) g(n,j)=g(n,j1)f(pj)(g(pjn,j1)i=1j1f(pi))
g ( n , j − 1 ) g(n,j - 1) g(n,j1) 扣掉以 p j p_j pj作为最小素因子的贡献,那么显然 p j ≤ n p_j \leq \sqrt n pjn ,因此只需要筛出 n \sqrt n n 范围内的素数用来转移。
(基于容斥的转移,感性理解一下)
显然可以DP,第二维可以滚动节约空间,由于转移要用到 ⌊ n p j ⌋ \lfloor\frac{n}{p_j}\rfloor pjn,而 ⌊ n p j ⌋ \lfloor\frac{n}{p_j}\rfloor pjn最多只有 2 n 2\sqrt n 2n 个值,可以预处理将这些值取出来离散化,然后递推DP转移即可筛出 g g g

考虑 g ( n , 0 ) g(n,0) g(n,0)的取值,观察一下会发现 g ( n , j ) g(n,j) g(n,j)是由 g ( n , j − 1 ) g(n,j - 1) g(n,j1)去掉最小素因子为 p j p_j pj的合数的贡献,这实际上是借用了埃筛的思想,最后 g ( n , ∣ P ∣ ) g(n,|P|) g(n,P)就是[1,n] 所有参数为素数的 f f f函数值的和, g ( n , 0 ) g(n,0) g(n,0)应取所有数贡献(即将所有数都当成素数求 ∑ i = 1 n f ( i ) \sum_{i = 1}^nf(i) i=1nf(i)

	sqr = sqrt(n);
	for(ll i = 1,j; i <= n; i = j + 1) {
		j = n / (n / i);
		w[++tot] = n / i;
		ll p = n / i % mod;
		g1[tot] = (p * (p + 1) / 2 % mod - 1 + mod) % mod;
		g2[tot] = (p - 1) % mod;
		if(n / i <= sqr) id1[n / i] = tot;
		else id2[n / (n / i)] = tot;
	}
	for(int i = 1; i <= num; i++) {
		for(int j = 1; j <= tot && 1ll * pri[i] * pri[i] <= w[j]; j++) {
			ll t = w[j] / pri[i];
			ll k = t <= sqr ? id1[t] : id2[n / t];
			g1[j] -= (g1[k] - sum1[i - 1] + mod) % mod * pri[i] % mod;
			g2[j] -= (g2[k] - sum2[i - 1] + mod) % mod;
			if(g1[j] < 0) g1[j] += mod;
			if(g2[j] < 0) g2[j] += mod;
		}
	}

现在考虑来筛 ∑ i = 1 n f ( i ) \sum_{i = 1}^n f(i) i=1nf(i),定义 S ( n , j ) = ∑ i = 1 n f ( i ) [ i 的 最 小 素 因 子 &gt; p j ] S(n,j) = \sum_{i = 1}^nf(i)[i的最小素因子 &gt; p_j] S(n,j)=i=1nf(i)[i>pj]
将式子分为质数和合数部分: S ( n , j ) = g ( n , ∣ P ∣ ) − ∑ i = 1 j f ( p i ) + ∑ k = j + 1 , p k ≤ n ∑ e = 1 , p k e ≤ n f ( p k e ) ∗ ( S ( ⌊ n p k e ⌋ , k ) + [ e ! = 1 ] ) S(n,j) = g(n,|P|) - \sum_{i = 1}^jf(p_i) + \sum_{k = j + 1,p_k \leq \sqrt n}\sum_{e = 1,p_k^e \leq n}f(p_k^e)*(S(\lfloor\frac{n}{p_k^e}\rfloor,k) + [e != 1]) S(n,j)=g(n,P)i=1jf(pi)+k=j+1,pkn e=1,pkenf(pke)(S(pken,k)+[e!=1])
左边部分即质数部分的和,因为要求最小素因子 > p j p_j pj,扣掉 ≤ p j \leq p_j pj的质数的贡献。
右边部分即合数部分的和,枚举最小素因子 p k p_k pk,即最小素因子的幂次 e e e,将 f ( p k e ) f(p_k^e) f(pke)提出来进行转移,因为 f f f函数是一个积性函数,提出来计算不会影响答案,因为 f ( p k e ) f(p_k^e) f(pke)也被筛掉了,后面再加上,当 e = 1 e = 1 e=1时,加上的是一个质数的贡献,这个贡献在前面已经加上,这里特判 e != 1
最后答案就是 S ( n , 0 ) + f ( 1 ) S(n,0) + f(1) S(n,0)+f(1),由于 1 既不是质数也不是合数,因此最后要加上 1 的贡献。
S ( n , j ) S(n,j) S(n,j) 可以递归求,出于玄学,重复计算的项并不多(或没有重复计算),不用记忆化。


那么这题的题解:
当 p 为质数且 p != 2, f ( p ) = p − 1 f(p) = p - 1 f(p)=p1
当 p = 2, f ( p ) = p + 1 f(p) = p + 1 f(p)=p+1
将素数项都考虑成 p - 1,最后特判 2,把答案加回来。
按套路将答案贡献拆成素数部分和合数部分。
将素数部分的贡献拆成两个部分: ∑ i = 1 ∣ P ∣ f ( p i ) = ∑ i = 1 ∣ P ∣ p i − ∑ i = 1 ∣ P ∣ 1 \sum_{i = 1}^{|P|}f(p_i) = \sum_{i = 1}^{|P|}p_i - \sum_{i = 1}^{|P|}1 i=1Pf(pi)=i=1Ppii=1P1
这两个部分都是积性函数,可以分别递推求两个部分的 g ( n , ∣ P ∣ ) g(n,|P|) g(n,P)
然后按照套路递归求 S ( n , j ) S(n,j) S(n,j) 即可


代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
int num,tot;
bool ispri[maxn];
ll pri[maxn],sum1[maxn],sum2[maxn];
ll w[maxn],g1[maxn],g2[maxn],id1[maxn],id2[maxn];
ll n,sqr;
void sieve(int n) {
	ispri[0] = ispri[1] = true;
	num = 0;
	for(int i = 2; i <= n; i++) {
		if(!ispri[i]) {
			pri[++num] = i;
			sum1[num] = (sum1[num - 1] + i) % mod;
			sum2[num] = sum2[num - 1] + 1;
		}
		for(int j = 1; j <= num && i * pri[j] <= n; j++) {
			ispri[i * pri[j]] = true;
			if(i % pri[j] == 0) break;
		}
	}
}
ll S(ll x,int y) {
	if(pri[y] >= x) return 0;
	ll k = x <= sqr ? id1[x] : id2[n / x];
	ll ans = ((g1[k] - sum1[y] - g2[k] + sum2[y]) % mod + mod) % mod;
	if(x >= 2 && y == 0) ans += 2;
	for(int i = y + 1; i <= num && 1ll * pri[i] * pri[i] <= x; i++) {
		for(ll e = 1,pe = pri[i]; pe <= x; e++,pe = pe * pri[i]) {
			ans += (pri[i] ^ e) % mod * (S(x / pe,i) + (e != 1)) % mod;
			ans %= mod;
		}
	}
	return ans;
}
int main() {
	sieve(maxn - 10);
	scanf("%lld",&n);
	sqr = sqrt(n);
	for(ll i = 1,j; i <= n; i = j + 1) {
		j = n / (n / i);
		w[++tot] = n / i;
		ll p = n / i % mod;
		g1[tot] = (p * (p + 1) / 2 % mod - 1 + mod) % mod;
		g2[tot] = (p - 1) % mod;
		if(n / i <= sqr) id1[n / i] = tot;
		else id2[n / (n / i)] = tot;
	}
	for(int i = 1; i <= num; i++) {
		for(int j = 1; j <= tot && 1ll * pri[i] * pri[i] <= w[j]; j++) {
			ll t = w[j] / pri[i];
			ll k = t <= sqr ? id1[t] : id2[n / t];
			g1[j] -= (g1[k] - sum1[i - 1] + mod) % mod * pri[i] % mod;
			g2[j] -= (g2[k] - sum2[i - 1] + mod) % mod;
			if(g1[j] < 0) g1[j] += mod;
			if(g2[j] < 0) g2[j] += mod;
		}
	}
	ll res = (S(n,0) + 1) % mod;
	printf("%lld\n",res);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值