1 条题解

  • 0
    @ 2025-8-24 22:49:04

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar XeCtera
    reverie

    搬运于2025-08-24 22:49:04,当前版本为作者最后更新于2023-08-12 20:35:24,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目传送门:P9511 『STA - R3』大豆

    0. 前言

    给出一种时间复杂度 O(km3/4logm)O\left(\dfrac{km^{3/4}}{\log m}\right),空间复杂度 O(m)O(\sqrt m) 的做法。

    常数较小且不难实现。

    1. 记号约定

    Sf(n)=i=1nf(i)\displaystyle {\rm S}f(n)=\sum_{i=1}^nf(i)

    x/y=xyx/y=\lfloor\frac xy\rfloor,且运算优先级低于乘法,即 x/yzx/yz 表示 x/(yz)x/(yz)

    2. 题解 (Part 1)

    不难发现 {a}\{a\} 的大豆化序列 {b}\{b\} 满足

    bm=amd=2mbm/db_m=a_m-\sum_{d=2}^mb_{m/d}

    注意到这与杜教筛使用的式子具有相似性。

    具体地,设 I(n)=1I(n)=1 为常函数,gI=fg*I=f,则

    Sg(m)=Sf(m)d=2mSg(m/d){\rm S}g(m)={\rm S}f(m)-\sum_{d=2}^m{\rm S}g(m/d)

    当此式成立时,显然也能反推出 gI=fg*I=f

    本题中只需令 f,gf,g 分别为 {a},{b}\{a\},\{b\} 的差分,那么有 g=fμg=f*\mu

    所求即为 S(fμk)(m){\rm S}(f*\mu^k)(m) 的值。(μk\mu^k 表示 kkμ\mu 卷积的结果)

    使用足够厉害的数论科技即可解决。

    下面开始介绍科技。

    3. Dirichlet 双曲线法

    这里可以把它当成整除分块的替代品。

    相比于整除分块,它在常数等方面有一定优势。

    考虑求如下式子的值:

    $${\rm S}(f*g)(n)=\sum_{i=1}^nf(i){\rm S}g(n/i)=\sum_{ij\le n}f(i)g(j) $$

    (假设 f,g,Sf,Sgf,g,{\rm S}f,{\rm S}g 的点值都可以 O(1)O(1) 得到)

    设阈值 x,yx,y 满足 xy=nxy=n

    我们对 ixi\le x 的部分和 jyj\le y 的部分分别求和,再减去公共部分。

    $$\sum_{i\le x}f(i){\rm S}g(n/i)+\sum_{j\le y}g(j){\rm S}f(n/j)-{\rm S}f(x){\rm S}g(y) $$

    可以 O(x+y)O(x+y) 完成计算。

    通常取 x=y=nx=y=\sqrt n 得最优复杂度 O(n)O(\sqrt n)

    4. 块筛卷积

    首先我们有一条重要性质:x/y/z=x/yzx/y/z=x/yz

    根据这条性质,再结合整除分块的一些分析可知:

    在求 Sf(n){\rm S}f(n) 时,我们往往只需用到 ${\rm S}f(1)\sim {\rm S}f(\lfloor\sqrt n\rfloor),{\rm S}f(n/\lfloor\sqrt n\rfloor)\sim{\rm S}f(n/1)$ 的值。

    称之为 ff(在 nn 处)的块筛

    块筛卷积:假设有一个狄利克雷卷积式 fg=hf*g=h,其中 f,gf,g 的块筛已知,试求 hh 的块筛。

    可以直接用 Dirichlet 双曲线法,对块筛的每个位置暴力求解。

    $${\rm S}h(n)=\sum_{i=1}^{\sqrt n}f(i){\rm S}g(n/i)+\sum_{i=1}^{\sqrt n}g(i){\rm S}f(n/i)-{\rm S}f(\sqrt n){\rm S}g(\sqrt n) $$

    (开方运算默认下取整,下同)

    复杂度是 $\displaystyle O\left(\sum_{i=1}^{\sqrt n}\sqrt i+\sum_{i=1}^{\sqrt n}\sqrt{n/i}\right)=O(n^{3/4})$。

    可以进一步优化,但这里暂且不提。

    5. 杜教筛

    假设有一个狄利克雷卷积式 fg=hf*g=h,其中 g,hg,h 的块筛已知,试求 ff 的块筛。

    事实上前面的做法仍然适用,只需要对式子做个移项:

    $${\rm S}f(n)={\rm S}h(n)-\sum_{i=1}^{\sqrt n}f(i){\rm S}g(n/i)-\sum_{i=2}^{\sqrt n}g(i){\rm S}f(n/i)+{\rm S}f(\sqrt n){\rm S}g(\sqrt n) $$

    递推即可,复杂度同样为 O(n3/4)O(n^{3/4})。(同样可以优化,但先不提)

    这种方法通常被称为杜教筛

    若使用普通数组存储块筛,并像上面这样递推求解,这种实现或许可以称为 dp 式杜教筛。

    目前广为流传的则是递归 + map / unordered_map 记忆化存储的写法。我暂且称之为记搜式杜教筛。

    事实上 dp 式实现的常数要小很多,并且复杂度也更加直观。

    不明白为什么大家都用记搜式。难过。

    6. 整除运算的优化

    底层卡常技巧。

    预处理 1m1\sim\sqrt m 的倒数,将整除改为浮点乘。

    若实现得当,可以保证代码中所有整除运算的除数都在 1m1\sim\sqrt m 以内。将会有良好的优化效果。

    注意事项:

    (1+1e-15)/x 这样的方式来计算 x 的倒数,而非 1./x 。后者的精度损失可能导致出错。

    7. 题解 (Part 2)

    回到本题,我们要求 S(fμk)(m){\rm S}(f*\mu^k)(m) 的值,其中 ff 的块筛已知。

    朴素做法是对 ffkk 次杜教筛。

    事实上,dp 式杜教筛 + 整除优化的实现已经能够以 O(km3/4)O(km^{3/4}) 的复杂度通过。

    并且应该可以吊打 std。

    但这还不够令人满意。下面给出一种更优秀的做法。

    注意到 μ\mu 是积性函数,比 ff 具有更好的性质。

    考虑先求 μk\mu^k 的块筛,然后容易 O(m)O(\sqrt m) 得到 S(fμk)(m){\rm S}(f*\mu^k)(m)

    对于积性函数 ff,记 fj(n)=f(n)[minp(n)>pj]f_j(n)=f(n)[{\rm minp}(n)>p_j]

    其中 minp(n){\rm minp}(n)nn 的最小质因子(钦定 minp(1)=+{\rm minp}(1)=+\infty),pjp_j 是第 jj 个质数。

    取阈值 KK 满足 pK+1p_{K+1} 略大于 m4\sqrt[4]m,那么 fKf_K 有优秀的性质:

    fK(1)fK(m)f_K(1)\sim f_K(\sqrt m) 中,只有 11 和大于 m4\sqrt[4]m 的质数处的值非零。

    回忆 Dirichlet 双曲线法的式子:

    $${\rm S}h(n)=\sum_{i=1}^{\sqrt n}f(i){\rm S}g(n/i)+\sum_{i=1}^{\sqrt n}g(i){\rm S}f(n/i)-{\rm S}f(\sqrt n){\rm S}g(\sqrt n) $$

    发现复杂度可优化至 $O(\pi(\sqrt n))=O\left(\dfrac{\sqrt n}{\ln n}\right)$。

    我们用 μKIK=ε\mu_K*I_K=\varepsilon 杜教筛求出 μK\mu_K 的块筛,再做块筛卷积得到 μKk\mu^k_K 的块筛。

    这部分的复杂度为 O(km3/4lnm)O\left(\dfrac{km^{3/4}}{\ln m}\right)

    最后的问题是,如何得到 IKI_K 的块筛,又如何将 μKk\mu_K^k 的块筛转化为 μk\mu^k 的块筛?

    事实上,采取 min_25 筛第一部分的 dp 即可实现。

    给出 II 的转移方程作为示例:

    $$\begin{aligned} {\rm S}I_0(n)&={\rm S}I(n)\\&=n\\ {\rm S}I_j(n)&={\rm S}I_{j-1}(n)-I(p_j){\rm S}I_{j-1}(n/p_j)\\&={\rm S}I_{j-1}(n)-{\rm S}I_{j-1}(n/p_j)\quad(j>0) \end{aligned}$$

    μk\mu^k 同理,读者可以尝试自行推出。

    这部分的复杂度为 $O(k\sqrt m\cdot\pi(\sqrt[4]m))=O\left(\dfrac{km^{3/4}}{\ln m}\right)$。

    因此时间复杂度为 O(km3/4lnm)O\left(\dfrac{km^{3/4}}{\ln m}\right)。空间复杂度为 O(m)O(\sqrt m)

    8. 代码实现

    变量名有些不太一致,注释以上文为准。

    #include<bits/stdc++.h>
    #define rep(i,l,r) for (int i=l; i<=r; ++i)
    #define rrep(i,r,l) for (int i=r; i>=l; --i)
    #define cpy(a,b) memcpy(a,b,sizeof a)
    using namespace std;
    typedef long long i64;
    
    const int N=1e5+8,M=1e4+8,P=23068673;
    const double I=1+1e-14;
    i64 n; int T,K,sq,cnt,k,a[M],pri[M];
    bitset<N/2>v; double inv[N];
    int s1[N],s2[N],s3[N]; i64 S1[N],S2[N],S3[N];
    struct Node { int f,g; }pos[M];
    
    void init(int n) {
        int sq=sqrt(n),n2=(n>>1);
        for (int i=3; i<=sq; i+=2) if (!v[i>>1]) // 埃氏筛
            for (int j=(i*i>>1); j<=n2; j+=i) v[j]=1;
        pri[cnt=1]=2;
        rep(i,1,n2) if (!v[i]) pri[++cnt]=(i<<1|1);
        pri[cnt+1]=n+1;
        rep(i,1,n) inv[i]=I/i;
    }
    inline i64 dv(i64 x,int y) { return x*inv[y]; } // 整除优化
    
    void calc1() {
        k=upper_bound(pri+1,pri+cnt+1,sqrt(sq))-pri;
        assert(pri[k+1]*pri[k+1]>sq);
        rep(i,1,sq) s1[i]=i;
        rep(i,1,sq) S1[i]=dv(n,i);
        rep(j,1,k) {
            int p=pri[j],t=dv(sq,p); i64 tn=dv(n,p);
            rep(i,1,t) S1[i]-=S1[i*p];
            rep(i,t+1,sq) S1[i]-=s1[dv(tn,i)];
            for (int i2=t,i=sq; i2; --i2)
                for (int lim=i2*p; i>=lim; --i) s1[i]-=s1[i2];
        } // 得到 I_K 的块筛
        rep(i,1,sq) s2[i]=2-s1[i];
        rrep(i,sq,1) {
            i64 n2=dv(n,i),s=0;
            int B=sqrt(n2),t,j=k+1;
            for ( ; (t=i*pri[j])<=sq; ++j)
                s+=S2[t]-S1[t];
            for ( ; pri[j]<=B; ++j)
                t=dv(n2,pri[j]),
                s+=s2[t]-s1[t];
            S2[i]=1-S1[i]-s+(i64)s1[B]*s2[B];
        } // 得到 μ_K 的块筛
        cpy(s1,s2),cpy(S1,S2);
    }
    
    void mul(int *sf,i64 *Sf,int *sg,i64 *Sg,int *sh,i64 *Sh) { // f*g=h 块筛卷积
        rep(j,k+1,cnt) {
            int p=pri[j];
            pos[j]={sf[p]-sf[p-1],sg[p]-sg[p-1]};
        }
        rep(i,1,sq) {
            i64 n2=dv(n,i),s=0;
            int B=sqrt(n2),t,j=k+1;
            for ( ; (t=i*pri[j])<=sq; ++j)
                s+=pos[j].f*Sg[t]+pos[j].g*Sf[t];
            for ( ; pri[j]<=B; ++j)
                t=dv(n2,pri[j]),
                s+=(i64)pos[j].f*sg[t]+(i64)pos[j].g*sf[t];
            Sh[i]=Sf[i]+Sg[i]+s-(i64)sf[B]*sg[B];
        }
        rrep(i,sq,1) sh[i]=sf[i]+sg[i]-1;
    }
    
    void solve() {
        rep(j,1,k) {
            int p=pri[j],t=dv(sq,p); i64 tn=dv(n,p);
            rep(tmp,1,K) {
                rep(i,1,t) S1[i]-=S1[i*p];
                rep(i,t+1,sq) S1[i]-=s1[dv(tn,i)];
                for (int i2=t,i=sq; i2; --i2)
                    for (int lim=i2*p; i>=lim; --i) s1[i]-=s1[i2];
            }
        } // 得到 μ^k 的块筛
        rep(i,1,sq) s2[i]=a[(i-1)%T+1];
        rep(i,1,sq) S2[i]=a[(dv(n,i)-1)%T+1]; // 得到 f 的块筛
        i64 ans=0;
        rep(i,1,sq) ans=(ans+i64(s1[i]-s1[i-1])*S2[i])%P;
        rep(i,1,sq) ans=(ans+i64(s2[i]-s2[i-1])*S1[i])%P;
        ans=(ans-(i64)s1[sq]*s2[sq])%P;
        printf("%lld\n",ans<0?ans+P:ans);
    }
    
    int main() {
        scanf("%d%lld%d",&T,&n,&K);
        rep(i,1,T) scanf("%d",a+i),a[i]%=P;
        init(sq=sqrt(n));
        calc1();
        if (K>1) mul(s1,S1,s1,S1,s2,S2);
        if (K>2) mul(s1,S1,s2,S2,s3,S3);
        if (K==2) cpy(s1,s2),cpy(S1,S2);
        if (K==3) cpy(s1,s3),cpy(S1,S3);
        solve();
        return 0;
    }
    
    • 1

    信息

    ID
    8878
    时间
    2500ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者