洛谷P4213 [模板]杜教筛(Sum) 杜教筛


洛谷P4213 [模板]杜教筛(Sum)


标签

  • 杜教筛

前言

  • 我的csdn和博客园是同步的,欢迎来访danzh-博客园~
  • 杜教筛的模板~
  • 前置知识是线性筛,莫比乌斯反演,狄利克雷卷积。如果不会,需要稍微学习一下再来学杜教筛~

简明题意

  • 需要求 μ \mu μ ϕ \phi ϕ的前缀和,范围在 2 31 − 1 2^{31}−1 2311,多组询问。

思路

  • 杜教筛的模板。首先,杜教筛的核心是下面这个式子:(g函数你可以任意取)
    g ( 1 ) ∗ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) S [ n i ] g(1)*S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S[\frac ni] g(1)S(n)=i=1n(fg)(i)i=2ng(i)S[in]

我快乐的来推导一下吧~很简单
首先我们任意取一个g函数,g和f的狄利克雷卷积的前缀和
∑ i = 1 n ( g ∗ f ) ( i ) = ∑ i = 1 n ∑ d ∣ i g ( d ) f ( [ i d ] ) \sum_{i=1}^n(g*f)(i)=\sum_{i=1}^n\sum_{d|i}g(d)f([\frac id]) i=1n(gf)(i)=i=1ndig(d)f([di])
改为枚举d:
∑ d = 1 n ∑ i = 1 n [ d ∣ i ] g ( d ) f ( [ i d ] ) \sum_{d=1}^n\sum_{i=1}^n[d|i]g(d)f([\frac id]) d=1ni=1n[di]g(d)f([di])
然后 ∑ i = 1 n [ d ∣ i ] \sum\limits_{i=1}^n[d|i] i=1n[di]这个式子可以很容易变换上限,就成了 ∑ i = 1 [ n d ] 1 \sum\limits_{i=1}^{[\frac nd]}1 i=1[dn]1。变了上限之后枚举项i成了原来的 [ 1 d ] [\frac 1d] [d1],所以后面的 f ( [ i d ] ) f([\frac id]) f([di])就成了 f ( [ i ∗ d d ] ) = f ( i ) f([\frac {i*d}d])=f(i) f([did])=f(i),所以原式就成了:
∑ d = 1 n ∑ i = 1 [ n d ] g ( d ) S ( i ) \sum_{d=1}^n\sum_{i=1}^{[\frac nd]}g(d)S(i) d=1ni=1[dn]g(d)S(i)
然后我们呢移项:
∑ d = 1 n g ( d ) ∑ i = 1 [ n d ] f ( i ) \sum_{d=1}^ng(d)\sum_{i=1}^{[\frac nd]}f(i) d=1ng(d)i=1[dn]f(i)
很显然啊后面的式子就是f的前缀和,然后就成了:
p r e [ f ∗ g ] ( n ) = ∑ d = 1 n g ( d ) S ( [ n d ] ) pre[f*g](n)=\sum_{d=1}^ng(d)S([\frac nd]) pre[fg](n)=d=1ng(d)S([dn])
然后自行前缀和一下:
∑ d = 1 n g ( d ) S ( [ n d ] ) − ∑ d = 2 n g ( d ) S ( [ n d ] ) = S ( n ) ∗ g ( 1 ) \sum_{d=1}^ng(d)S([\frac nd])-\sum_{d=2}^ng(d)S([\frac nd])=S(n)*g(1) d=1ng(d)S([dn])d=2ng(d)S([dn])=S(n)g(1)

  • 观察只要求出了 g g g的前缀和,最后面的式子就分块了。因此对于一个积性函数,我们只要找到一个
  1. 容易求前缀和
  2. 容易求和原函数的狄利克雷卷积的函数

的函数g,那么就能完成。

  • 先看 μ \mu μ函数。容易知道 μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ,那就令 g = I g=I g=I,那么先把 g g g I I I代换,再把我们要求的 μ \mu μ代换掉 f f f
    ∑ i = 1 n ( μ ∗ g ) ( i ) = ∑ i = 1 n ( μ ∗ I ) ( i ) \sum\limits_{i=1}^n(\mu*g)(i)=\sum\limits_{i=1}^n(\mu*I)(i) i=1n(μg)(i)=i=1n(μI)(i)

μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ是常用的式子。证明:

  • 由狄利克雷卷积 μ ∗ I = ∑ d ∣ n μ ( d ) ∗ I ( [ n d ] ) \mu*I=\sum\limits_{d|n}\mu(d)*I([\frac nd]) μI=dnμ(d)I([dn])
  • I ( x ) = 1 I(x)=1 I(x)=1,带入上式,可以知道 μ ∗ I = ∑ d ∣ n μ ( d ) \mu*I=\sum\limits_{d|n}\mu(d) μI=dnμ(d)
  • 而由莫比乌斯反演(也可以称为莫比乌斯函数性质)可以知道 ∑ d ∣ n μ ( d ) = [ n = = 1 ] = ϵ \sum\limits_{d|n}\mu(d)=[n==1]=\epsilon dnμ(d)=[n==1]=ϵ,代入原式就有:
    μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ
  • 然后很容易知道 μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ,所以原式就成了:
    ∑ i = 1 n ϵ ( i ) = [ n > = 1 ] \sum\limits_{i=1}^n\epsilon(i)=[n>=1] i=1nϵ(i)=[n>=1]
  • 简直太好求了。把这个带入原式就成了:
    S ( n ) = [ n > = 1 ] − ∑ i = 2 n S [ n i ] S(n)=[n>=1]-\sum_{i=2}^nS[\frac ni] S(n)=[n>=1]i=2nS[in]
  • 现在问题就是求第二个式子 ∑ i = 2 n g ( i ) S [ n i ] \sum\limits_{i=2}^ng(i)S[\frac ni] i=2ng(i)S[in],分块一下,简直简单了。

----------------------------------------------分鸽线----------------------------------------------

  • 现在求 ϕ \phi ϕ的前缀和。跟前面差不多很简单鸭。自行推导吧~

----------------------------------------------分鸽线----------------------------------------------

  • 首先这个是可以记忆化搜索的,然而会T的很惨QAQ
  • 优化。虽然杜教筛是低于线性复杂度的,但是有多组询问鸭!多组询问时,线性筛的复杂度反而优于杜教筛(线性筛 O ( n O(n O(n)预处理,然后 O ( 1 ) O(1) O(1)查询。但杜教筛…)所以,我们可以提前线性筛一下,预处理出能筛出的 μ 和 ϕ \mu和\phi μϕ的前缀和,然后杜教筛就能快很多!!!

注意事项

  • 提前线性筛大概筛5e6比较合适~

总结

  • 这个得熟记常见的狄利克雷卷积,然后找g函数的时候就会很快!这里总结一下常见的狄利克雷卷积:
  • μ ∗ I = ϵ \mu*I=\epsilon μI=ϵ
  • ϕ ∗ I = i d \phi*I=id ϕI=id

AC代码

#include<cstdio>
#include<unordered_map>
using namespace std;

const int maxn = 5e6 + 10;

bool no_prime[maxn];
int prime[maxn], mu[maxn], phi[maxn], p_mu[maxn];
long long p_phi[maxn];
int shai(int n)
{
	int cnt = 0;
	mu[1] = phi[1] = 1;

	for (int i = 2; i <= n; i++)
	{
		if (!no_prime[i])
			prime[++cnt] = i, mu[i] = -1, phi[i] = i - 1;

		for (int j = 1; j <= cnt && prime[j] * i <= n; j++)
		{
			no_prime[i * prime[j]] = 1;
			mu[i * prime[j]] = i % prime[j] == 0 ? 0 : -mu[i];
			phi[i * prime[j]] = i % prime[j] == 0 ? phi[i] * prime[j] : phi[i] * (prime[j] - 1);
			if (i % prime[j] == 0) break;
		}
	}

	for (int i = 1; i <= n; i++)
		p_mu[i] = p_mu[i - 1] + mu[i], p_phi[i] = p_phi[i - 1] + phi[i];

	return cnt;
}

unordered_map<int, int> rec_mu;
unordered_map<int, long long> rec_phi;

int pre_mu(int n)
{
	if (n <= maxn - 10) return p_mu[n];
	if (rec_mu[n]) return rec_mu[n];

	int l = 2, r, ans = (n >= 1);
	while (l <= n)
	{
		r = n / (n / l);
		ans -= (r - l + 1) * pre_mu(n / l);
		l = r + 1;
	}
	return rec_mu[n] = ans;
}

long long pre_phi(int n)
{
	if (n <= maxn - 10) return p_phi[n];
	if (rec_phi[n]) return rec_phi[n];

	int l = 2, r;

	long long ans = n % 2 == 0 ? 1ll*n / 2ll * (n + 1) : (n + 1ll) / 2ll * n;
	while (l <= n)
	{
		r = n / (n / l);
		ans -= (r - l + 1) * pre_phi(n / l);
		if (l == 2147483647) break;
		l = r + 1;
	}
	return rec_phi[n] = ans;
}

void solve()
{
	shai(maxn - 10);

	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n;
		scanf("%d", &n);

		printf("%lld %d\n", pre_phi(n), pre_mu(n));
	}
}

int main()
{
	solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值