欧拉函数+卷积+莫比乌斯函数+莫比乌斯反演+整除分块+杜教筛

Powered by:NEFU AB_IN

欧拉函数

定义:对于一个正整数n,小于n且和n互质的正整数(包括1)的个数,记作φ(n) 。

欧拉函数通式:
其中的所有质因数,。特别的

所以根据通式可以直接求出一个数的欧拉值

ll euler(ll n){
    ll ans = n;
    for(int i = 2; i * i <= n; i ++){
        if(!(n % i)){
            ans = ans / i * (i - 1);
            while(!(n % i)) n /= i;
        }
    }
    if(n > 1) ans = ans / n * (n - 1);
    return ans;
}

性质:

  • 欧拉函数为积性函数。

    积性函数:对于函数,当,
    完全积性函数:特别地,对于任意的,即,也有
    所以
    当且仅当,即互质
    特别地,假如不互质,则
    这个便为通式。
    以上证明均略。

  • 衍生出的性质:
    当且仅当为奇数时。

  • 对于一个质数
    无需证明,根据定义即可推出。
    所以,即可在线性筛的时候,打出欧拉函数的表

      namespace prime_sieve_phi{
          const int N = 1e7 + 5;
          int  cnt, sum, ans, prime[N], pre[N], phi[N];
          bool flag[N];
          void init()
          {
              memset(flag, 1, sizeof(flag));
              flag[1] = 0;
              cnt = 0;
              phi[1] = 1;
              for(int i = 2; i <= N; i++)
              {
                  if(flag[i])
                  {
                      prime[++cnt] = i;
                      pre[i] = cnt;
                      phi[i] = i - 1;
                  }
                  for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++)
                  {
                      flag[prime[j] * i] = 0;
                      if(i % prime[j] == 0) {
                          phi[i * prime[j]] = phi[i] * prime[j];
                          break;
                      }
                      phi[i * prime[j]] = phi[i] * (prime[j] - 1);
                  }
              }
          }
      }
      using namespace prime_sieve_phi;
  • 对于一个质数
    证明:

    • 首先,不互质的数有个(即),不互质的数有个,……,不互质的数有个。
    • 其次,提取公因式,得
    • 最后,根据性质二,得

  • 的因数(包括和它自己)的欧拉函数之和等于
    也就是(后面有)

    经典的应用为

  • 时,是偶数。

    证明:
    首先前提:若,则
    互质,则
    我们假设,则
    则当时,,与前提矛盾,故假设不成立。
    所以
    说明了时,互质,互质,也就是说:**时,与互质的数是成对出现的**,所以是偶数。

  • 对于一个正整数,小于且和互质的正整数(包括)的和,记作(乱叫的)

证明:
* $n=1$,略
* $n>2$,由于此时,**与$n$互质的数是成对出现**,且每一对里除了$n$以外的数为$m$与$n-m$,则它们的和便为$n$。由于会重复计算,故除以$2$,与$n$互质的数的个数为$φ(n)$,得证。

狄利克雷卷积

狄利克雷卷积是函数之间的运算

性质:

  • 假如做狄利克雷卷积的两个函数 都是积性函数,那么 也是积性函数
  • 满足交换律,结合律,分配律

常见的积性函数及狄利克雷卷积

  • 幂函数
  • 单位函数 (幂函数的情况)
  • 不变函数 (幂函数的情况)
  • 欧拉函数
  • 莫比乌斯函数 (左式和上式可推出)
  • 狄利克雷卷积单位元
  • 因子个数函数
  • 因子和函数
  • 因子函数
  • 其中完全积性函数

莫比乌斯函数

定义:

性质:

  • 莫比乌斯函数为积性函数
    故可以线性筛出来。

      namespace prime_sieve_mu{
          const int N = 1e7 + 5;
          int  cnt, sum, ans, prime[N], pre[N], mu[N];
          bool flag[N];
          void init()
          {
              memset(flag, 1, sizeof(flag));
              flag[1] = 0;
              mu[1] = 1;
              cnt = 0;
              for(int i = 2; i <= N; i++)
              {
                  if(flag[i])
                  {
                      prime[++cnt] = i;
                      pre[i] = cnt;
                      mu[i] = -1;
                  }
                  for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++)
                  {
                      flag[prime[j] * i] = 0;
                      if(i % prime[j] == 0) {
                          mu[prime[j] * i] = 0;
                          break;
                      }
                      else{
                          mu[prime[j] * i] = -mu[i]; 
                          //互异因子
                      }
                  }
              }
          }
      }
      using namespace prime_sieve_mu;
  • 有平方因子的数的莫比乌斯函数值为


  • 如果狄利克雷卷积看懂了,这个式子其实就是
    这个可以说是最常用的性质

    经典的应用为
    证明:由定义得

  • 这个式子其实就是
    这个便为莫比乌斯函数和欧拉函数的关系
    这个式子的另外一个变式为

莫比乌斯反演

两种形式:

  • 约数形式
    简单证明:
  • 倍数形式

例题:

P3455 [POI2007]ZAP-Queries

传送门:https://www.luogu.com.cn/problem/P3455

每一个推导,我都尽力把坑都讲一下,帮助理解。
帮助理解:

  • 变化前:
  • 变化后:

这两个的含义是不一样的!!!

  • 变化前的的因子为,比如

    • ,
    • ,
    • ,
    • ,
    • ,
    • ,
  • 变化后的:式子其实是这样的:,比如

    • ,

    • ,

    • ,

    • ,

    • ,

    • ,

      就是从中是的倍数的数。

所以说两个的含义不同,千万不要混淆,我这么写只是为了后面写的时候方便推导。

公式推出来了,那后面那一坨怎么求呢?

整除分块

对于这种式子:
很明显,处理起来可以做到,但通过打表可以发现有许多的值是一样的,并且成块状分布,那么就可以求出这一个块的。这样一个块一个块地求,就会将复杂度降低到

比如上面的式子的代码为

for(int l = 1, r; l <= n; l = r + 1)
{
    r = n / (n / l);
    ans += (r - l + 1) * (n / l);
}

所以例题代码为

#include<bits/stdc++.h>
using namespace std;
#define ll                          long long
#define ull                         unsigned long long
#define ld                          long double
#define db                          double
#define all(x)                      (x).begin(),(x).end()
#define F                           first
#define S                           second
#define Mp                          make_pair
#define mset(s, _)                  memset(s, _, sizeof(s))
#define lowbit(x)                   ((x) &-(x))
#define IOS                         ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl                        "\n"
typedef pair<int, int>               pii;
typedef pair<ll, ll>                 pll;
namespace prime_sieve_mu{
    const int N = 5e4 + 5;
    int  cnt, ans, prime[N], mu[N], sum[N], pre[N];
    bool flag[N];
    inline void init(){
        memset(flag, 1, sizeof(flag));
        flag[1] = 0;
        mu[1] = 1;
        cnt = 0;
        for(int i = 2; i <= N; i++)
        {
            if(flag[i]){
                prime[++cnt] = i;
                pre[i] = cnt;
                mu[i] = -1;
            }
            for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++){
                flag[prime[j] * i] = 0;
                if(i % prime[j] == 0) {
                    mu[prime[j] * i] = 0;
                    break;
                }
                else{
                    mu[prime[j] * i] = -mu[i];
                }
            }
        }
        for(int i = 1; i <= N; i++) {
            sum[i] = sum[i - 1] + mu[i];
        }
    }
}
using namespace prime_sieve_mu;
int a, b, d, n;
int main()
{
    scanf("%d", &n);
    init();
    while(n --){
        ll ans = 0;
        scanf("%d%d%d", &a, &b, &d);
        a /= d; b /= d; //这里要先整除d,因为上界不是a,而是a/d
        int tmp = min(a, b);
        for(int l = 1, r; l <= tmp; l = r + 1){
            r = min(a / (a / l), b / (b / l));
            ans += (1ll) * (sum[r] - sum[l - 1]) * (a / l) * (b / l);
            // (1ll) 是要加的
        }
        printf("%lld\n", ans);
    }
    return 0;
}

P2522 [HAOI2011]Problem b

传送门:https://www.luogu.com.cn/problem/P2522

和上一个题一样,运用一下容斥即可。

#include<bits/stdc++.h>
using namespace std;
#define ll                          long long
#define ull                         unsigned long long
#define ld                          long double
#define db                          double
#define all(x)                      (x).begin(),(x).end()
#define F                           first
#define S                           second
#define Mp                          make_pair
#define mset(s, _)                  memset(s, _, sizeof(s))
#define lowbit(x)                   ((x) &-(x))
#define IOS                         ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl                        "\n"
typedef pair<int, int>               pii;
typedef pair<ll, ll>                 pll;
namespace prime_sieve_mu{
    const int N = 5e4 + 5;
    int  cnt, ans, prime[N], mu[N], sum[N], pre[N];
    bool flag[N];
    inline void init(){
        memset(flag, 1, sizeof(flag));
        flag[1] = 0;
        mu[1] = 1;
        cnt = 0;
        for(int i = 2; i <= N; i++)
        {
            if(flag[i]){
                prime[++cnt] = i;
                pre[i] = cnt;
                mu[i] = -1;
            }
            for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++){
                flag[prime[j] * i] = 0;
                if(i % prime[j] == 0) {
                    mu[prime[j] * i] = 0;
                    break;
                }
                else{
                    mu[prime[j] * i] = -mu[i];
                }
            }
        }
        for(int i = 1; i <= N; i++) {
            sum[i] = sum[i - 1] + mu[i];
        }
    }
}
using namespace prime_sieve_mu;
int a, b, c, d, k, n;

ll solve(int a, int b){
    a /= k; b /= k;
    int tmp = min(a, b);
    ll ans = 0;
    for(int l = 1, r; l <= tmp; l = r + 1){
        r = min(a / (a / l), b / (b / l));
        ans += (1ll) * (a / l) * (b / l) * (sum[r] - sum[l - 1]);
    }
    return ans;
}

int main()
{
    scanf("%d", &n);
    init();
    while(n --){
        cin >> a >> b >> c >> d >> k;
        printf("%lld\n", solve(b, d) - solve(a - 1, d) - solve(c - 1, b) + solve(a - 1, c - 1)); 
    }
    return 0;
}

P3327 [SDOI2015]约数个数和

传送门:https://www.luogu.com.cn/problem/P3327

首先有个前置知识

好了,我们可以推式子了
可以看出后面这一坨是可以整除分块+预处理的,那为什么上一题的 不用预处理?
因为上个题的这个式子是可以平推过去的,整除分块可以现求,而现在这个式子,每个的取值都会决定不同的整除分块的值,所以我们需要把从的整除分块值都求出来,到时候带就行了。

最后,这个式子其实也是可以整除分块的,没用前缀和多可惜呀 (滑稽)

#include<bits/stdc++.h>
using namespace std;
#define ll                          long long
#define ull                         unsigned long long
#define ld                          long double
#define db                          double
#define all(x)                      (x).begin(),(x).end()
#define F                           first
#define S                           second
#define Mp                          make_pair
#define mset(s, _)                  memset(s, _, sizeof(s))
#define lowbit(x)                   ((x) &-(x))
#define IOS                         ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl                        "\n"
typedef pair<int, int>               pii;
typedef pair<ll, ll>                 pll;
namespace prime_sieve_mu{
    const int N = 5e4 + 5;
    int  cnt, ans, prime[N], pre[N], mu[N];
    ll sum[N], f[N];
    bool flag[N];
    inline void init()
    {
        memset(flag, 1, sizeof(flag));
        flag[1] = 0;
        mu[1] = 1;
        cnt = 0;
        for(int i = 2; i <= N; i++)
        {
            if(flag[i])
            {
                prime[++cnt] = i;
                pre[i] = cnt;
                mu[i] = -1;
            }
            for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++)
            {
                flag[prime[j] * i] = 0;
                if(i % prime[j] == 0) {
                    mu[prime[j] * i] = 0;
                    break;
                }
                else{
                    mu[prime[j] * i] = -mu[i];
                }
            }
        }
        for(int i = 1; i <= N; i ++){
            sum[i] = sum[i - 1] + mu[i];
            for(int l = 1, r; l <= i; l = r + 1){
                r = i / (i / l);
                f[i] += (1ll) * (r - l + 1) * (i / l);
            }
        }
    }
}
using namespace prime_sieve_mu;
int t, n, m;
int main()
{
    init();
    scanf("%d", &t);
    while(t --){
        scanf("%d%d", &n, &m);
        int tmp = min(n, m);
        ll ans = 0;
        for(int l = 1, r; l <= tmp; l = r + 1){
            r = min(n / (n / l), m / (m / l));
            ans += (1ll) * (sum[r] - sum[l - 1]) * f[n / l] * f[m / l];
        }
        printf("%lld\n", ans);
    }
    return 0;
}

杜教筛

定义:杜教筛是以低于线性的时间复杂度来计算积性函数的前缀和的神奇筛法

需要计算的式子:

为了解决这个问题,我们构造两个积性函数。使得


基本式子我们这就推出来了,重点是如何选择正确的,只要

  • 前缀和很好求
  • 容易整除分块

那么当我们对后面的式子进行整除分块时,求的复杂度为

比如:

    • 根据式子 ,我们需要找满足
    • 翻到狄利克雷卷积那一节扫一眼,,明显的前缀和好求,就是嘛,也容易分块。
    • ,带入便得

  • 就不给出推导过程了。

    可以通过等差数列得到。


  • (逛博客时发现大佬给的例子,像下面这种一眼看不出结果的式子)

    • 既然一眼看不出是什么,那么我们可以先写出狄利克雷卷积:
    • 我们当然希望可以消掉,正巧有个,那么就可以这么配了
      那么,

P4213 【模板】杜教筛(Sum)

传送门:https://www.luogu.com.cn/problem/P4213

#include<bits/stdc++.h>
using namespace std;
#define ll                          long long
#define ull                         unsigned long long
#define ld                          long double
#define db                          double
#define all(x)                      (x).begin(),(x).end()
#define F                           first
#define S                           second
#define Mp                          make_pair
#define mset(s, _)                  memset(s, _, sizeof(s))
#define lowbit(x)                   ((x) &-(x))
#define IOS                         ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl                        "\n"
typedef pair<int, int>               pii;
typedef pair<ll, ll>                 pll;

namespace prime_sieve_phi_mu{
    const int N = 6e6 + 5;
    int  cnt, ans, prime[N], pre[N], phi[N], mu[N];
    bool flag[N];
    ll sum_phi[N], sum_mu[N];
    inline void init()
    {
        memset(flag, 1, sizeof(flag));
        flag[1] = 0;
        cnt = 0;
        phi[1] = 1;
        mu[1] = 1;
        for(int i = 2; i <= N; i++){
            if(flag[i]){
                prime[++cnt] = i;
                pre[i] = cnt;
                phi[i] = i - 1;
                mu[i] = -1;
            }
            for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++){
                flag[prime[j] * i] = 0;
                if(i % prime[j] == 0) {
                    phi[i * prime[j]] = phi[i] * prime[j];
                    mu[prime[j] * i] = 0;
                    break;
                }
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
                mu[prime[j] * i] = -mu[i];
            }
        }
        for(int i = 1; i <= N; i++){
            sum_phi[i] = sum_phi[i - 1] + phi[i];
            sum_mu[i] = sum_mu[i - 1] + mu[i];
        }
    }
}
using namespace prime_sieve_phi_mu;

unordered_map < ll, ll > phi_w; //算过的一定要记录下来
unordered_map < ll, ll > mu_w;

ll djs_phi(ll x){
    if(x <= N) return sum_phi[x];
    if(phi_w[x]) return phi_w[x];
    ll ans = x * (x + 1) / 2;
    for(ll l = 2, r; l <= x; l = r + 1){
        r = x / (x / l);
        ans -= (r - l + 1) * (djs_phi(x / l));
    }
    return phi_w[x] = ans;
}

ll djs_mu(ll x){
    if(x <= N) return sum_mu[x];
    if(mu_w[x]) return mu_w[x];
    ll ans = 1;
    for(ll l = 2, r; l <= x; l = r + 1){
        r = x / (x / l);
        ans -= (r - l + 1) * (djs_mu(x / l));
    }
    return mu_w[x] = ans;
}

int t;
ll n;
int main()
{
    init();
    scanf("%d", &t);
    while(t --){
        scanf("%lld", &n);
        printf("%lld %lld\n", djs_phi(n), djs_mu(n));
    }
    return 0;
}

至此入门就结束了,最近碰到了个好题,正好写一下。

phi and phi

传送门:https://ac.nowcoder.com/acm/contest/10845/F


愉快地推柿子

很多结论在前面我都到了,如果又看不懂的地方,可以翻到前面偷偷看一眼。
是不是很麻烦?
那我们不用的性质,用的的再试一遍!!
是不是这个简单多了!

  • 但题目要求的是的所有值。

  • 在枚举时,之间时,相当于整除分块的思想,值都一样,的贡献不变。

  • 所以对做一个差分,最后再求一个前缀和即可。

#include<bits/stdc++.h>
using namespace std;
#define ll                          long long
#define ull                         unsigned long long
#define ld                          long double
#define db                          double
#define all(x)                      (x).begin(),(x).end()
#define F                           first
#define S                           second
#define Mp                          make_pair
#define mset(s, _)                  memset(s, _, sizeof(s))
#define lowbit(x)                   ((x) &-(x))
#define IOS                         ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl                        "\n"
typedef pair<int, int>               pii;
typedef pair<ll, ll>                 pll;

const int mod = 1e9 + 7;
int mul(int x, int y){return 1ll * x * y % mod;}
int dec(int x, int y){return x >= y ? x - y : x + mod - y;}
int add(int x, int y){return x + y >= mod ? x + y - mod : x + y;}
namespace prime_sieve_phi{
    const int N = 5e6 + 6;
    int  cnt, prime[N], pre[N], phi[N];
    bool flag[N];
    inline void init()
    {
        memset(flag, 1, sizeof(flag));
        flag[1] = 0;
        cnt = 0;
        phi[1] = 1;
        for(int i = 2; i <= N; i++)
        {
            if(flag[i])
            {
                prime[++cnt] = i;
                pre[i] = cnt;
                phi[i] = i - 1;
            }
            for(int j = 1; j <= cnt && 1ll * prime[j] * i <= N; j++)
            {
                flag[prime[j] * i] = 0;
                if(i % prime[j] == 0) {
                    phi[i * prime[j]] = phi[i] * prime[j];
                    break;
                }
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            }
        }
    }
}
using namespace prime_sieve_phi;
int n;
ll ans[N];
int main()
{
    init();
    cin >> n;
    for(int T = 1;T <= n; T++) {
        ll sum = 0;
        for(int i = 1;i <= n / T; i++) {
            sum = add(sum, phi[i * T]);
            ans[i * T] = add(ans[i * T], mul(phi[T], mul(sum, sum)));
            ans[(i + 1) * T] = dec(ans[(i + 1) * T], mul(phi[T], mul(sum, sum)));
        }
    }
    for(int i = 1;i <= n; i++) {
        ans[i] = add(ans[i], ans[i - 1]);
        printf("%lld\n",ans[i]);
    }
    return 0;
}

完结。

#学习路径#
全部评论
感谢参与【创作者计划2期·技术干货场】!欢迎更多牛油来写干货,瓜分总计20000元奖励!!技术干货场活动链接:https://www.nowcoder.com/link/czz2jsghtlq(参与奖马克杯将于每周五结算,敬请期待~)
点赞 回复 分享
发布于 2021-03-13 10:20
写的很详细
点赞 回复 分享
发布于 2021-04-21 19:36

相关推荐

一名愚蠢的人类:多少games小鬼留下了羡慕的泪水
投递荣耀等公司10个岗位
点赞 评论 收藏
分享
我是小红是我:学校换成中南
点赞 评论 收藏
分享
6 3 评论
分享
牛客网
牛客企业服务