zoukankan      html  css  js  c++  java
  • 杜教筛

    最近在透彻一些数学知识,多项式啊,线性代数啊,杜教筛也逃不掉。。。

    洛咕博客的题解都会搬过来的,然后我会整理一遍所有文章。。。

    然后下面是正题

    我们现在已知积性函数(f(n)),求(f(n))的前n项和(S(n))

    由于(f(n))是积性函数,所以可以通过线性筛在(O(n))的复杂度解决

    但是由于有杜教筛这种东西使得复杂度被进一步优化

    我们先扯扯Dirichlet卷积,假设(f(n))(g(n))是两个数论函数,则(f(n))(g(n))的Dirichlet卷积定义为(displaystyle(f*g)(n)=sum_{d|n}f(d)gleft(frac{n}{d} ight))或者写一个直观点的(displaystyle (f*g)(n)=sum_{ab=n}f(a)g(b))

    然后呢有一个性质就是如果f和g都是积性函数,他们的Dirichlet卷积也是积性函数。。。

    然后呢我们假设(h(n)=(f*g)(n))然后呢推式子

    先是(h(n))的前缀和式子

    (displaystyle sum_{i=1}^nh(i)=sum_{i=1}^ng(i)Sleft(leftlfloorfrac n i ight floor ight))

    这是什么鬼woc

    (displaystylesum_{i=1}^nh(i))

    写成Dirichlet卷积的形式

    (displaystylesum_{i=1}^nsum_{d|i}g(d)fleft(frac i d ight))

    把d提取出来

    (displaystyle sum_{d=1}^ng(d)sum_{d|i}fleft(frac i d ight))

    i是d的倍数,考虑让i除以d

    (displaystyle sum_{d=1}^ng(d)sum_{i=1}^{leftlfloorfrac n d ight floor}f( i))

    然后呢吧后面表示为前缀和的性质(把d字母再替换一下)

    (displaystyle sum_{i=1}^ng(i)Sleft(leftlfloorfrac n i ight floor ight))

    然后呢既然推出了这(h(n))前缀和的另一种表示形式

    那么我们先写一个无聊的式子(displaystyle g(1)S(n)=sum_{i=1}^ng(i)Sleft(leftlfloorfrac n i ight floor ight)-sum_{i=2}^ng(i)Sleft(leftlfloorfrac n i ight floor ight))

    你会问我们推出来这么无聊的东西有什么用,把后面移项不就成恒等式了么

    别急,右面的第一项不是上面推得(h(i))的前缀和么。。。

    所以(displaystyle g(1)S(n)=sum_{i=1}^nh(i)-sum_{i=2}^ng(i)Sleft(leftlfloorfrac n i ight floor ight))

    观察这个式子,就是(S)的递推式喽

    根据数论分块思想 递归求S

    然后呢...注意每求出一个前缀和用map存一下

    一般我们做杜教筛时候,要自己钦定一个(g)(h=f*g),使得(g(x))能在O(1)获得,(h(x))的前缀和能在(O(1))获得,其实一般(g(x)=1)

    然后呢求欧拉函数(varphi)的前缀和,我们令(g(x)=1),则(h(x)=x)也就是id

    所以(displaystyle S(n)=sum_{i=1}^ni-sum_{i=2}^nSleft(leftlfloorfrac n i ight floor ight))

    然后呢根据等差数列有关知识(displaystyle sum_{i=1}^ni=frac{n(n+1)}2),可以(O(1))求出

    后面就直接递归就行了

    然后呢是莫比乌斯函数(mu),其实更简单,我们还是令(g(x)=1),则(h(x)=[x=1]),也就是Dirichlet卷积得单位元函数

    所以(displaystyle S(n)=1-sum_{i=2}^nSleft(leftlfloorfrac n i ight floor ight))

    然后呢说实现

    这个算法时间复杂度是(O(n^frac{3}{4}))

    然后呢我不会证

    然后据说先线性筛出前面的一些值可以把复杂度优化为(O(n^{frac{2}{3}}))

    然后呢这是一份洛谷板子题的代码

    #include <bits/stdc++.h>
    #define maxn 3000010
    using namespace std;
    
    map<int, long long> ans_phi;
    map<int, int> ans_mu;
    bool vis[maxn];
    int prime[maxn], tot;
    long long phi[maxn];
    int mu[maxn];
    
    void prework()
    {
        phi[1] = 1;
        mu[1] = 1;
        for (int i = 2; i <= 3000000; i++)
        {
            if (vis[i] == 0)
            { 
                prime[++tot] = i;
                mu[i] = -1;
                phi[i] = i - 1;
            }
            for (int j = 1; j <= tot && prime[j] * i <= 3000000; j++)
            {
                vis[i * prime[j]] = 1;
                if (i % prime[j] == 0)
                {
                    mu[i * prime[j]] = 0;
                    phi[i * prime[j]] = phi[i] * prime[j];
                    break;
                }
                else
                {
                    mu[i * prime[j]] = -mu[i];
                    phi[i * prime[j]] = phi[i] * (prime[j] - 1);
                }
            }
            mu[i] += mu[i - 1];
            phi[i] += phi[i - 1];
        }
    }
    
    long long S_phi(int n)
    {
        if (n <= 3000000)
            return phi[n];
        if (ans_phi.count(n))
            return ans_phi[n];
        long long ans = 1LL * (n + 1) * n / 2;
        for (int l = 2, r; l <= n; l = r + 1)
        {
            r= n / (n / l);
            ans -= (r - l + 1) * S_phi(n / l);
        }
        return ans_phi[n] = ans;
    }
    
    int S_mu(int n)
    {
        if (n <= 3000000)
            return mu[n];
        if (ans_mu.count(n))
            return ans_mu[n];
        int ans = 1;
        for (int l = 2, r; l <= n; l = r + 1)
        {
            r = n / (n / l);
            ans -= (r - l + 1) * S_mu(n / l);
        }
        return ans_mu[n] = ans;
    }
    
    void read(int &x)
    {
        static char ch;
        x = 0;
        ch = getchar();
        while (!isdigit(ch))
            ch = getchar();
        while (isdigit(ch))
        {
            x = x * 10 + ch - 48;
            ch = getchar();
        }
    }
    
    int main()
    {
        prework();
        int T;
        read(T);
        for (int n, i = 1; i <= T; i++)
        {
            read(n);
            printf("%lld %d
    ", S_phi(n), S_mu(n));
        }
        return 0;
    }
    

    参考资料:洛谷题解

  • 相关阅读:
    shell log
    Python:列出列表中所有元素的组合可能
    scrapy 停止爬虫
    shell split log by data
    mitmproxy 配置
    插件reres的使用,替换网站的js文件
    解决小米Note adb调试无法发现设备
    md5 计算文件一致性
    【Frida Hook 学习记录】Frida Hook Android 常用方法
    监控神器普罗米修斯Prometheus安装配置
  • 原文地址:https://www.cnblogs.com/oier/p/9596145.html
Copyright © 2011-2022 走看看