[loj6053]简单的函数【min_25筛】

【题目链接】
  https://loj.ac/problem/6053
【题解】
  min_25筛的模板题。
  min_25筛可以解决以下一类积性函数的求和问题。
  1. f ( x ) ( x ∈ P ) f(x)(x \in P) f(x)(xP)可以通过多项式表达出来。
  2. f ( x k ) ( x ∈ P ) f(x^k)(x \in P) f(xk)(xP)可以快速算出。
  这道题中, f ( x ) = x f(x) = x f(x)=x^ 1 = x − 1 ( x ∈ P 且 x ≠ 2 ) 1 = x - 1(x \in P 且x \neq 2) 1=x1(xPx̸=2)所以条件1满足,条件2显然也是满足的。
  part1
  我们先来考虑一个问题,如何求 F ( T ) = ∑ i = 1 T f ( i ) ( i ∈ P , T ∈ ⌊ N / i ⌋ ) F(T)=\sum_{i=1}^{T}f(i)(i\in P,T\in \left\lfloor N/i\right\rfloor ) F(T)=i=1Tf(i)(iP,TN/i),T是N通过除法能得到的数,大约有 2 N 2 \sqrt{N} 2N 种取值。
  不难发现, ∑ i = 1 N f ( i ) ( i ∈ P ) = ∑ i = 1 N i − 1 ( i ∈ P ) + 2 \sum_{i=1}^{N}f(i)(i\in P) = \sum_{i=1}^{N}i-1(i\in P) + 2 i=1Nf(i)(iP)=i=1Ni1(iP)+2 因为只有2异或了后会加1。
   那么我们要求的就是质数的和以及质数的个数。
   首先筛出 1.. N 1..\sqrt{N} 1..N 中的所有质数,记作 p [ 1 ] . . p [ M ] p[1]..p[M] p[1]..p[M]
   记函数 G k ( n , m ) G_{k}(n, m) Gk(n,m)表示 ∑ i = 1 n i k ( i ∈ P 或 i 的 最 小 质 因 子 > p [ m ] ) \sum_{i = 1}^{n}i^k(i \in P或i的最小质因子>p[m]) i=1nik(iPi>p[m])
   那么相当于求出 G 0 ( T , M ) , G 1 ( T , M ) G_{0}(T,M),G_{1}(T,M) G0(T,M),G1(T,M)
   考虑从 G k ( n , m − 1 ) G_{k}(n,m-1) Gk(n,m1)推到 G k ( n , m ) G_{k}(n,m) Gk(n,m),就是从中去除最小质因子是 p [ m ] p[m] p[m]的,于是可以推出:
   G k ( n , m ) = G k ( n , m − 1 ) − p [ m ] k ∗ ( G k ( ⌊ n / p [ m ] ⌋ , m − 1 ) − G k ( p [ m ] − 1 , m − 1 ) ) G_{k}(n,m)=G_{k}(n,m-1)-p[m]^k*(G_{k}(\left\lfloor n/p[m]\right\rfloor,m-1)-G_{k}(p[m]-1,m-1)) Gk(n,m)=Gk(n,m1)p[m]k(Gk(n/p[m],m1)Gk(p[m]1,m1))
   相当于强制选出一个 p [ m ] p[m] p[m],剩下的随便选,但要重新加上有比 p [ m ] p[m] p[m]更小的质因子的方案。
   边界条件是 G k ( n , 0 ) = ∑ i = 1 n i k G_{k}(n,0)=\sum_{i=1}^{n}i^k Gk(n,0)=i=1nik,可以 O ( 1 ) O(1) O(1)计算。
   由于我们只要求 2 N 2\sqrt{N} 2N 个取值,可以将它们一起考虑。
   具体来说,我们从前往后枚举每一个质数 p [ 1 ] . . p [ m ] p[1]..p[m] p[1]..p[m]由于大的的取值不会影响小的,所以从后往前更新。
   现在的复杂度是 N ∗ ( s q r t N / l n N ) = O ( N / l n N ) \sqrt{N}*(sqrt{N}/ln{\sqrt{N}})=O(N/ln{\sqrt{N}}) N (sqrtN/lnN )=O(N/lnN )
   考虑优化,当 p [ m ] 2 > n p[m]^2>n p[m]2>n时, G k ( n , m ) = G k ( n , m − 1 ) G_{k}(n,m)=G_{k}(n,m-1) Gk(n,m)=Gk(n,m1),因为不可能存在一个有 p [ m ] p[m] p[m]的因子但不是质数的数,所以每次有一段前缀不用算。
   现在的复杂度是 ∑ T ∑ i = 1 m [ p [ i ] 2 ≤ T ] ≈ O ( N 3 / 4 / l n N ) \sum_{T}\sum_{i=1}^{m}[p[i]^2 \leq T]\approx O(N^{3/4}/ln{N}) Ti=1m[p[i]2T]O(N3/4/lnN)?,我不会证,但肯定比 O ( N 3 / 4 ) O(N^{3/4}) O(N3/4)小。
   Part2
   处理出了 F F F接下来就简单了。
   记 S ( n , m ) S(n,m) S(n,m) ∑ i = 1 n f ( i ) [ i 的 最 小 质 因 子 ≥ p [ m ] ] \sum_{i=1}^{n}f(i)[i的最小质因子\geq p[m]] i=1nf(i)[ip[m]],我们要求的是 S ( N , 1 ) + f ( 1 ) S(N,1)+f(1) S(N,1)+f(1)
   由于我们已经知道了 F F F(质数的答案和)。现在要加入非质数的答案。还是考虑枚举质数:
   S ( n , m ) = F ( n ) − F ( p [ m ] − 1 ) + ∑ i = m M ∑ j = 1 p [ i ] j + 1 ≤ N ( f ( p [ i ] j ) ∗ S ( n / p [ i ] j , i + 1 ) + f ( p [ i ] j + 1 ) ) S(n,m)=F(n)-F(p[m]-1)+\sum_{i=m}^{M}\sum_{j=1}^{p[i]^{j+1}\leq N}(f(p[i]^j)*S(n/p[i]^j,i+1)+f(p[i]^{j+1})) S(n,m)=F(n)F(p[m]1)+i=mMj=1p[i]j+1N(f(p[i]j)S(n/p[i]j,i+1)+f(p[i]j+1))
   由于 p [ M ] ≤ N p[M]\leq \sqrt{N} p[M]N 所以它的 F F F值也是已知的。
   边界条件是 n &lt; p [ m ] n&lt;p[m] n<p[m]时,值为0。
   这个相当于是把上一部分的递归实现,所以复杂度与Part1相同。
   总时间复杂度: O ( N 3 / 4 / l n N ) O(N^{3/4}/ln{N}) O(N3/4/lnN)一般能做到 1 e 10 1e10 1e10
【代码】(为了便于调试很多地方写的比较烦)

/* - - - - - - - - - - - - - - -
	User : 		VanishD
	problem :	loj-6053
	Points : 	Min_25 
- - - - - - - - - - - - - - - */
# include <bits/stdc++.h>
# define 	ll 		long long
# define 	inf 	0x3f3f3f3f
# define 	P 		1000000007
# define 	N 		1001000
using namespace std;
ll use[N], p[N], pre[N], bak[N], pre0[N], pre1[N], bak0[N], bak1[N];
ll sum, nt, cnt;
int pnum;
const ll inv2 = 5e8 + 4; 
void get_p(int n){
	use[1] = true; 
	for (int i = 2; i <= n; i++){
		if (!use[i]) p[++pnum] = i;
		for (ll j = 1; j <= pnum && 1ll * i * p[j] <= n; j++){
			use[i * p[j]] = true;
			if (i % p[j] == 0) break;
		}
	}
}
ll F(ll p, int k){
	if (k == 0) return 1;
	return p ^ k;
}
void get_Pow0(){
	for (int i = 1; i <= nt; i++)
		pre0[i] = i, bak0[i] = sum / i;
	for (int i = 1, prep = 1, bakp = nt; i <= pnum; i++){
		while (prep <= nt && p[i] * p[i] > prep) prep++;
		while (bakp >= 1  && p[i] * p[i] > sum / bakp) bakp--;
		for (int j = 1; j <= bakp; j++){
			ll x = sum / j;
			ll  tmp1 = (x <= nt) ? pre0[x] : bak0[sum / x],
				tmp2 = (x / p[i] <= nt) ? pre0[x / p[i]] : bak0[sum / (x / p[i])],
				tmp3 = pre0[p[i] - 1];
			bak0[j] = (tmp1 - 1 * (tmp2 - tmp3) + P) % P;
		}			
		for (int j = nt; j >= prep; j--){
			ll x = j;
			ll  tmp1 = (x <= nt) ? pre0[x] : bak0[sum / x],
				tmp2 = (x / p[i] <= nt) ? pre0[x / p[i]] : bak0[sum / (x / p[i])],
				tmp3 = pre0[p[i] - 1];
			pre0[j] = (tmp1 - 1 * (tmp2 - tmp3) + P) % P;
		}			
	}
	for (int i = 1; i <= nt; i++) pre0[i] -= 1, bak0[i] -= 1;
}
void get_Pow1(){
	for (int i = 1; i <= nt; i++){
		pre1[i] = 1ll * i * (i + 1) % P * inv2 % P;
		ll tmp = (sum / i) % P;
		bak1[i] = tmp * (tmp + 1) % P * inv2 % P;
	}
	for (int i = 1, prep = 1, bakp = nt; i <= pnum; i++){
		while (prep <= nt && p[i] * p[i] > prep) prep++;
		while (bakp >= 1  && p[i] * p[i] > sum / bakp) bakp--;
		for (int j = 1; j <= bakp; j++){
			ll x = sum / j;
			ll  tmp1 = (x <= nt) ? pre1[x] : bak1[sum / x],
				tmp2 = (x / p[i] <= nt) ? pre1[x / p[i]] : bak1[sum / (x / p[i])],
				tmp3 = pre1[p[i] - 1];
			bak1[j] = (tmp1 - p[i] * (tmp2 - tmp3) % P + P) % P;
		}			
		for (int j = nt; j >= prep; j--){
			ll x = j;
			ll  tmp1 = (x <= nt) ? pre1[x] : bak1[sum / x],
				tmp2 = (x / p[i] <= nt) ? pre1[x / p[i]] : bak1[sum / (x / p[i])],
				tmp3 = pre1[p[i] - 1];
			pre1[j] = (tmp1 - p[i] * (tmp2 - tmp3) % P + P) % P;
		}
	}
	for (int i = 1; i <= nt; i++) pre1[i] -= 1, bak1[i] -= 1;
}
ll get_s(ll n, int m){
	if (n <= 1 || n < p[m]) return 0;
	ll ans = ((n > nt) ? bak[sum / n] : pre[n]) - pre[p[m] - 1];
	for (int i = m; i <= pnum && n >= 1ll * p[i] * p[i]; i++)
		for (ll j = 1, k = p[i]; k * p[i] <= n; j++, k *= p[i])
			ans = (ans + F(p[i], j) * get_s(n / k, i + 1) + F(p[i], j + 1)) % P;
	return ans;
}
int main(){
	ll n;
	scanf("%lld", &n);
	sum = n; nt = sqrt(n);
	get_p(nt);
	get_Pow0(); get_Pow1(); 
	for (int i = 1; i <= nt; i++){
		if (i == 1) pre[i] = 0;
			else pre[i] = (pre1[i] - pre0[i] + 2 + P) % P;
		bak[i] = (bak1[i] - bak0[i] + 2 + P) % P;
	}
	p[pnum + 1] = nt + 1;
	printf("%lld\n", (get_s(n, 1) + 1) % P);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值