杜教筛学习笔记

本文深入探讨了杜教筛算法在高效求解数论问题中的应用,特别是针对大规模数据集的前缀和计算。文章详细介绍了如何通过杜教筛算法优化计算过程,避免数据溢出,并提供了一系列实用的代码示例。
刀尖舞蹈

如果数据很容易爆long longlong\ longlong long的话,你有几种处理方式:

1:在爆炸的边缘试探

正确写法:ans=(ULL)n∗(n+1LL)/2LL;ans = (ULL) n * (n + 1LL) / 2LL;ans=(ULL)n(n+1LL)/2LL;

错误写法:ans=(ULL)n∗(n+1)/2;ans = (ULL) n * (n + 1) / 2;ans=(ULL)n(n+1)/2;

1LL1LL1LL表示加一个long longlong\ longlong long类型的111,尽量不要跨类型加。

2:int_128int\_128int_128禁术

3:利用语言特性(大数模乘)

return(LL)a∗b−(LL)((LDB)a∗b/p)∗p)return (LL) a * b - (LL)((LDB)a * b / p) * p)return(LL)ab(LL)((LDB)ab/p)p)

4:高精度(最不幸的)

5: 改python

杜教筛

用于在快于线性的时间内求一个函数的前缀和。

你的目标是求fff的前缀和F(n)F(n)F(n)

我们寻找一个好求前缀和的函数ggg,(一般是111函数)

构造h=f∗gh=f*gh=fg,这里∗*是狄利克雷卷积。记HHHhhh的前缀和。

那么有:

F(n)=H(n)−∑i=2ng(i)F(ni)g(1)F(n)={\frac{H(n)-\sum_{i=2}^ng(i)F(\frac{n}{i})}{g(1)}}F(n)=g(1)H(n)i=2ng(i)F(in)

我们对ni\frac{n}{i}in数论分块来做,这就要求H,gH,gH,g是可以快速求出前缀和的。

首先预处理一部分可以线性得到的FFF,一般预处理n23n^{\frac{2}{3}}n32为宜。

对于每个询问,跑杜教筛。可以用c++11c++11c++11特性下的unordered_mapunordered\_mapunordered_map来存储求过的FFF

ϕ\phiϕ的前缀和。

ϕ∗1=I=id1\phi*1=I=id_1ϕ1=I=id1

预处理就是一般的ϕ\phiϕ筛。

inline LL dlsphi(int n) {
    if (n < N) return F[n];
    if (q1[n]) return q1[n];
    LL res = 0;
    for (register int L = 2, R; R < INF && L <= n; L = R + 1) {
        R = n / (n / L);
        res += (ULL) (R - L + 1) * dlsphi(n / L);
    }
    return q1[n] = (ULL)n * (n + 1LL) / 2LL - res;
}

这里的nnnintintint级别的,所以要特殊处理下防止越界。

μ\muμ的前缀和

μ∗1=ϵ\mu*1=\epsilonμ1=ϵ

预处理也就是一般的μ\muμ筛。

inline int dlsmu(int n) {
    if (n < N) return F_[n];
    if (q2[n]) return q2[n];
    int res = (n >= 1);
    for (register int L = 2, R; R < INF && L <= n; L = R + 1) {
        R = n / (n / L);
        res -= (ULL) (R - L + 1) * dlsmu(n / L);
    }
    return q2[n] = res;
}
F(n)=∑i=1nϕ(i)∗i2F(n)=\sum_{i=1}^n\phi(i)*i^2F(n)=i=1nϕ(i)i2

f(i)=ϕ(i)∗i2f(i)=\phi(i)*i^2f(i)=ϕ(i)i2
看到了i2i^2i2,我们考虑让构造的ggg里面也有i2i^2i2达到消掉的目的。

g(i)=i2g(i)=i^2g(i)=i2h=f∗gh=f*gh=fg

h(i)=∑d∣ig(id)f(d)=i2∑d∣iϕ(d)=i3h(i)=\sum_{d|i}g(\frac{i}{d})f(d)=i^2\sum_{d|i}\phi(d)=i^3h(i)=dig(di)f(d)=i2diϕ(d)=i3

loj6229

目前遇到的最难的推式子题。。。

写一下推导的过程吧。。

要求∑i=1n∑j=1ilcm(i,j)gcd(i,j)\sum_{i=1}^n\sum_{j=1}^i\frac{lcm(i,j)}{gcd(i,j)}i=1nj=1igcd(i,j)lcm(i,j)

考虑求∑i=1n∑j=1nlcm(i,j)gcd(i,j)\sum_{i=1}^n\sum_{j=1}^n\frac{lcm(i,j)}{gcd(i,j)}i=1nj=1ngcd(i,j)lcm(i,j) 然后+n+n+n再除以222就是答案。

=∑i=1n∑j=1nijgcd2(i,j)=\sum_{i=1}^n\sum_{j=1}^n\frac{ij}{gcd^2(i,j)}=i=1nj=1ngcd2(i,j)ij

=∑d=1n1d2∑i=1n∑j=1nij[gcd(i,j)=d]=\sum_{d=1}^n\frac{1}{d^2}\sum_{i=1}^n\sum_{j=1}^nij[gcd(i,j)=d]=d=1nd21i=1nj=1nij[gcd(i,j)=d]

=∑d=1n∑i=1⌊nd⌋∑j=1⌊nd⌋ij[gcd(i,j)=1]=\sum_{d=1}^n\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij[gcd(i,j)=1]=d=1ni=1dnj=1dnij[gcd(i,j)=1]

=∑d=1n∑i=1⌊nd⌋∑j=1⌊nd⌋ij∑d′∣gcd(i,j)μ(d′)=\sum_{d=1}^n\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij\sum_{d&#x27;|gcd(i,j)}\mu(d&#x27;)=d=1ni=1dnj=1dnijdgcd(i,j)μ(d)

=∑d=1n∑d′=1⌊nd⌋μ(d′)(d′)2∑i=1⌊ndd′⌋∑j=1⌊ndd′⌋ij=\sum_{d=1}^n\sum_{d&#x27;=1}^{\lfloor\frac{n}{d}\rfloor}\mu(d&#x27;)(d&#x27;)^2\sum_{i=1}^{\lfloor\frac{n}{dd&#x27;}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{dd&#x27;}\rfloor}ij=d=1nd=1dnμ(d)(d)2i=1ddnj=1ddnij

q=d′dq=d&#x27;dq=dd

∑q=1n∑d′∣qμ(d′)(d′)2(∑i=1⌊nq⌋i)2\sum_{q=1}^n\sum_{d&#x27;|q}\mu(d&#x27;)(d&#x27;)^2(\sum_{i=1}^{\lfloor\frac{n}{q}\rfloor}i)^2q=1ndqμ(d)(d)2(i=1qni)2

f(i)=∑d∣iμ(d)d2f(i)=\sum_{d|i}\mu(d)d^2f(i)=diμ(d)d2,发现后面那坨是个数论分块的东西。
所以任务就是求f(i)f(i)f(i)的前缀和。

构造g(i)=i2,t(i)=μ(i)i2g(i)=i^2,t(i)=\mu(i)i^2g(i)=i2,t(i)=μ(i)i2,发现直接求h=f∗gh=f*gh=fg不太好搞

接下来就是见证奇迹的时刻。

f=t∗1h=f∗g=t∗g∗1f=t*1\quad\quad h=f*g=t*g*1f=t1h=fg=tg1

发现 t∗g=ϵt*g=\epsilontg=ϵ

证明:假设l=t∗gl=t*gl=tg

l(i)=∑d∣it(d)∗g(id)=∑d∣iμ(d)i2=i2[i==1]=ϵl(i)=\sum_{d|i}t(d)*g(\frac{i}{d})=\sum_{d|i}\mu(d)i^2=i^2[i==1]=\epsilonl(i)=dit(d)g(di)=diμ(d)i2=i2[i==1]=ϵ

所以:h=t∗g∗1=1h=t*g*1=1h=tg1=1

所以我们可以在O(1)O(1)O(1)的时间内算出H(n)=∑i=1nh(i)=nH(n)=\sum_{i=1}^nh(i)=nH(n)=i=1nh(i)=n

根据杜教筛的式子:

F(n)=H(n)−∑i=2ng(i)F(ni)g(1)F(n)=\frac{H(n)-\sum_{i=2}^ng(i)F(\frac{n}{i})}{g(1)}F(n)=g(1)H(n)i=2ng(i)F(in)

F(n)=H(n)−∑i=2ni2F(ni)F(n)=H(n)-\sum_{i=2}^ni^2F(\frac{n}{i})F(n)=H(n)i=2ni2F(in),平方和的区间和可以通过两个通项公式倒腾出来。

复杂度的话,简单分析来就外层是一个大数论分块O(n0.5)O(n^{0.5})O(n0.5),里面杜教筛的nnn依赖于外层数论分块的L,RL,RL,R

这个东西可以归结成一类的复杂度分析:

∑i=1nf(ni)×g(i)\sum_{i=1}^{n} f(\frac{n}{i}) \times g(i)i=1nf(in)×g(i),其中ggg可以使用杜教筛求前缀和。

考虑在求ggg的值的时候,杜教筛里面是有记忆化的。也就是一个数论分块,所以这次恰好会访问到那些上次外层调用杜教筛的地方。复杂度还是O(n23)O(n^\frac{2}{3})O(n32)

codes:codes:codes:

#include <cstdio>
#include <map>
using namespace std;
typedef long long LL;
map<int, LL> q;
const int N = 1e6 + 5, P = 1e9 + 7;
int prime[N], vis[N], tot;
LL F[N], mu[N], inv2, inv6;
int n;
inline LL q_p(LL a, LL b, LL p) {
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}
inline void eluer() {
    vis[1] = 1; mu[1] = 1;
    inv2 = q_p(2, P - 2, P);
    inv6 = q_p(6, P - 2, P);
    for (int i = 2; i < N; ++i) {
        if (!vis[i]) {
            prime[++tot] = i;
            mu[i] = ((1 - 1LL * i * i) % P + P) % P;
        }
        for (int j = 1; j <= tot; ++j) {
            LL cur = 1LL * i * prime[j];
            if (cur >= N) break;
            vis[cur] = 1;
            if (i % prime[j] == 0) {
                mu[cur] = mu[i];
                break;
            }
            else mu[cur] = (mu[i] * mu[prime[j]]) % P;
        }
    }
    for (int i = 1; i < N; ++i) F[i] = (F[i - 1] + mu[i]) % P;
}
inline LL calc(int n) {
    return inv6 * n % P * (n + 1) % P * (2 * n + 1) % P;
}
inline LL dls(int n) {
    if (n < N) return F[n];
    if (q[n]) return q[n];
    LL res = n;
    for (int L = 2, R; L <= n; L = R + 1) {
        R = n / (n / L);
        res = ((res - ((calc(R) - calc(L - 1)) % P + P) % P * dls(n / L) % P) % P + P) % P;
    }
    return q[n] = res;
}
int main() {
    scanf("%d", &n);
    eluer();
    LL ans = 0;
    for (int L = 1, R; L <= n; L = R + 1) {
        R = n / (n / L);
        LL res = ((dls(R) - dls(L - 1)) % P + P) % P;
        res = res * (1 + (n / L)) % P * (1 + (n / L)) % P;
        res = res * (n / L) % P * (n / L) % P;
        res = res * inv2 % P * inv2 % P;
        ans = (ans + res) % P;
    }
    printf("%lld\n", (ans + n) % P * inv2 % P);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值