首页 > 其他分享 >【学习笔记】初等数论

【学习笔记】初等数论

时间:2024-07-16 21:07:36浏览次数:12  
标签:em gcd 数论 hspace sum varphi int 笔记 初等

[学习笔记]初等数论

最大公约数 \(gcd\)

欧几里得算法(辗转相除法):

\[\gcd(a, b) = \gcd(b, a \bmod b) \]

代码:

int gcd(int a, int b){
    return b ? gcd(b, a%b) : a;
}

或者直接使用 __gcd(a, b)

辗转相减法:

\[\gcd(a, b) = \gcd(a, b-a) \]

推广到 \(n\) 项:

\[\gcd(a_1, a_2, \dots, a_n) = \gcd(a_1, a_2-a_1, a_3-a_2,\dots, a_n-a_{n-1}) \]

注意:因为 \(gcd\) 不可能是负数,所以当权值为负时答案应该取绝对值

线性筛

欧拉筛。不多讲了。

int n;
int prime[N], cnt;
bool is_prime[N];

void getprime(){
    for(int i=2; i<=n; i++) is_prime[i] = 1;
    for(int i=2; i<=n; i++){
        if(is_prime[i]){
            prime[++cnt] = i;
        }
        for(int j=1; j<=cnt && i*prime[j]<=n; j++){
            is_prime[i*prime[j]] = 0;
            if(i%prime[j] == 0) break;
        }
    }
}

积性函数

数论函数

数论函数指定义域为正整数的函数。数论函数也可以视作一个数列。

积性函数

若一个数论函数 \(f\) 满足,对所有互质的 \(n, m\),都满足 \(f(nm) = f(n)f(m)\),则称 \(f\) 是一个积性函数

此外,如果对所有 \(n, m\),都满足 \(f(nm) = f(n)f(m)\),则称 \(f\) 为完全积性函数

性质:都可以通过筛法来快速求解。

常见的积性函数:

欧拉函数 \(\varphi(n)\)

表示 \(\left[ 1, n \right]\) 中与 \(n\) 互质的数的数量。

欧拉公式:记 \(n = \prod p_i^{c_i}\),则 \(\varphi(n) = \prod (p_i-1)p_i^{c_i-1}\)。

性质:
\(\sum\limits_{d \mid n} \varphi(d) = n\)

莫比乌斯函数 \(\mu(n)\)

\(\mu(n) = \begin{cases} (-1)^k & n = p_1 \hspace{0.1em} p_2 \dots p_k \hspace{0.3em} (\hspace{0.1em}p_i\hspace{0.1em}是质数) \\ 0 & n \hspace{0.1em} 的某个质因子指数 \hspace{0.1em} c \ge 2 \end{cases}\)

性质:
\(\sum\limits_{d \mid n} \mu(d) = \left[n = 1\right]\)

单位函数 \(\epsilon(n)\)

\(\epsilon(n) = \begin{cases} 1 & n = 1 \\ 0 & n \neq 1 \end{cases}\)

筛 \(\varphi(n)\)

从欧拉筛出发考虑。下面的 \(i\) 指当前的数,\(p\) 指质数表中的质数。

  • 当 \(i\) 是质数时,显然 \(\varphi(i) = i-1\)。

  • 当 \(i \not\equiv 0 \hspace{0.3em} (\bmod \hspace{0.3em} p)\) 时,即 \(i, p\) 互质时,根据积性函数定义得:\(\varphi(ip) = \varphi(i)\varphi(p) = \varphi(i) \times (p-1)\)。

  • 当 \(i \equiv 0 \hspace{0.3em} (\bmod \hspace{0.3em} p)\) 时,即 \(p\) 是 \(i\) 的一个因子时。设 \(i\) 唯一分解中 \(p\) 的指数是 \(c\),因为 \(\varphi(p^c) = (p-1)p^{c-1}\),因此 \(\varphi(ip) = \varphi(i) \times p\)。

    解释:

    • 对于 \(\varphi(p^c) = (p-1)p^{c-1}\),\(\left[ 1, p\right]\) 内有 \(p-1\) 个数与其互质。所以 \(\left[ 1, p^c\right]\) 内有 \((p-1)p^{c-1}\) 个数与其互质。

    • 对于 \(\varphi(ip) = \varphi(i) \times p\)。因为 \(\varphi(ip) = \varphi(\frac{i}{p^c}) \cdot \varphi(p^{c+1})\),又因为 \(\frac{i}{p^c}\) 与 \(p^c\) 互质,所以 \(\varphi(\frac{i}{p^c}) \varphi(p^c) = \varphi(i)\)。又因为 \(\varphi(p^{c+1}) = (p-1)p^{c}\)。所以将 \(\varphi(\frac{i}{p^c})\) 和 \(\varphi(p^{c+1})\) 两项拆开消元后即可得到 \(\varphi(ip) = \varphi(i) \times p\)。

      也可通过欧拉公式(欧拉函数的定义)理解。

代码实现:

int n;
int prime[N], phi[N], cnt;
bool is_prime[N];

void getprime(){
    phi[1] = 1;
    for(int i=2; i<=n; i++) is_prime[i] = 1;
    for(int i=2; i<=n; i++){
        if(is_prime[i]){
            phi[i] = i-1;
            prime[++cnt] = i;
        }
        for(int j=1; j<=cnt && i*prime[j]<=n; j++){
            is_prime[i*prime[j]] = 0;
            if(i%prime[j] == 0){
                phi[i*prime[j]] = prime[j]*phi[i];
                break;
            }
            else phi[i*prime[j]] = (prime[j]-1)*phi[i];
        }
    }
}

筛 \(\mu(n)\)

还是考虑三种情况。

  • 当 \(i\) 是质数时,显然 \(\mu(i) = -1\)。
  • 当 \(i \not\equiv 0 \hspace{0.3em} (\bmod \hspace{0.3em} p)\) 时,即 \(i, p\) 互质时,根据积性函数定义得:\(\mu(ip) = \mu(i)\mu(p) = -\mu(i)\)。
  • 当 \(i \equiv 0 \hspace{0.3em} (\bmod \hspace{0.3em} p)\) 时,即 \(p\) 是 \(i\) 的一个因子时。根据该函数定义得 \(\mu(ip) = 0\)。

代码实现应该差不多。


P2568 GCD

首先我们枚举质数:

\[\sum_{p \in \rm prime} \sum_{i=1}^{n} \sum_{j=1}^{n} \left[ \gcd(i, j) = p \right] \]

变形:

\[\sum_{p \in \rm prime} \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \sum_{j=1}^{\lfloor \frac{n}{p} \rfloor} \left[ \gcd(i, j) = 1 \right] \]

接下来改变 \(j\) 的枚举上界(其中 \(-1\) 的原因是 \(i=j=1\) 时的答案会被重复统计,因此这里的 \(-1\) 是在第二层中的):

\[\sum_{p \in \rm prime} \left( \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \left( 2\sum_{j=1}^{i} \left[ \gcd(i, j) = 1 \right] \right) -1 \right) \]

此时发现 \(\sum\limits_{j=1}^{i} \left[ \gcd(i, j) = 1 \right]\) 就是 \(\varphi(i)\),故原式可化为:

\[\sum_{p \in \rm prime} \left( 2\sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \varphi(i) -1 \right) \]

可以前缀和求 \(\sum \varphi(i)\) 的值,最后枚举 \(p \in \rm prime\) \({\rm and}\) \(p \le n\) 并统计答案即可。

时间复杂度:\(O(n)\)。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e7 + 5;

int prime[N], phi[N], cnt;
bool is_prime[N];
ll ans, sum[N];

int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int n; cin>>n;
    phi[1] = 1;
    for(int i=2; i<=n; i++) is_prime[i] = 1;
    for(int i=2; i<=n; i++){
        if(is_prime[i]){
            phi[i] = i-1;
            prime[++cnt] = i;
        }
        for(int j=1; j<=cnt && i*prime[j]<=n; j++){
            is_prime[i*prime[j]] = 0;
            if(i%prime[j] == 0){
                phi[i*prime[j]] = prime[j]*phi[i];
                break;
            }
            else phi[i*prime[j]] = (prime[j]-1)*phi[i];
        }
    }
    for(int i=1; i<=n; i++) sum[i] = sum[i-1]+phi[i];
    for(int i=1; i<=cnt; i++) ans += 2*sum[n/prime[i]]-1;
    cout<<ans;
    return 0;
}

P10463 Interval GCD

辗转相减法(前置知识):

\[\gcd(a, b) = \gcd(a, b-a) \]

推广到 \(n\) 项:

\[\gcd(a_1, a_2, \dots, a_n) = \gcd(a_1, a_2-a_1, a_3-a_2,\dots, a_n-a_{n-1}) \]

看到题意中把 \(A_l, A_{l+1}, \dots, A_r\) 都加上 \(d\),自然想到差分。

令差分完之后的数组为 \(B\),那么 \(\gcd(B_{l+1}, B_{l+2}, \dots, B_r) = \gcd(A_{l+1}, A_{l+2}, \dots A_r)\),而 \(\gcd(B_l,B_{l+1}) \neq \gcd(A_l, A_{l+1})\),所以 \(A_l\) 需要单独维护。

以下用线段树维护差分数组的 \(\gcd\),用树状数组维护差分数组,最后通过前缀和求得 \(A_l\)。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 5e5+5;
#define lowbit(x) x&(-x)
#define ls(x) ((x)<<1)
#define rs(x) ((x)<<1|1)
#define gcd(x, y) abs(__gcd((x), (y)))

int n, m;
ll a[N], cha[N], c[N];
struct node{
    int l, r;
    ll v;
}tr[N<<2];

void add(int x, ll y){
    while(x <= n){
        c[x] += y;
        x += lowbit(x);
    }
}

ll sum(int x){
    ll ret = 0;
    while(x){
        ret += c[x];
        x -= lowbit(x);
    }
    return ret;
}

void pushup(int x){
    tr[x].v = gcd(tr[ls(x)].v, tr[rs(x)].v);
}

void buildtr(int x, int l, int r){
    tr[x].l = l, tr[x].r = r;
    if(l == r){
        tr[x].v = cha[l];
        return;
    }
    int mid = (l+r) >> 1;
    buildtr(ls(x), l, mid);
    buildtr(rs(x), mid+1, r);
    pushup(x);
}

void upd(int x, int tar, ll d){
    if(tr[x].l > tar || tr[x].r < tar)
        return;
    if(tr[x].l == tr[x].r && tr[x].l == tar){
        tr[x].v += d;
        return;
    }
    int mid = (tr[x].l+tr[x].r) >> 1;
    if(tar<=mid) upd(ls(x), tar, d);
    upd(rs(x), tar, d);
    pushup(x);
}

ll query(int x, int l, int r){
    if(tr[x].l > r || tr[x].r < l)
        return 0;
    if(tr[x].l >= l && tr[x].r <= r)
        return tr[x].v;
    return gcd(query(ls(x), l, r), query(rs(x), l, r));
}

int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>m;
    for(int i=1; i<=n; i++){
        cin>>a[i];
        cha[i] = a[i] - a[i-1];
        add(i, cha[i]);
    }
    buildtr(1, 1, n);
    while(m--){
        char op; int l, r; cin>>op>>l>>r;
        if(op == 'C'){
            ll d; cin>>d;
            upd(1, l, d);
            upd(1, r+1, -d);
            add(l, d);
            add(r+1, -d);
        } else if(op == 'Q'){
            cout<<gcd(sum(l), query(1, l+1, r))<<"\n";
        }
    }
    return 0;
}

USACO08DEC Patting Heads S

从枚举倍数的角度思考:对于一个数 \(i\),若它在原数组中出现了 \(cnt_i\) 次,那么 \(i\) 这个数会对它的每一个倍数产生 \(cnt_i\) 的贡献。这样就可以通过查询这样产生的贡献的总和来计算答案了。

注意最后要减去一个对自己的贡献。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+5;

int a[N], w[N], c[N];

int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int m = 0; // 值域
    int n; cin>>n;
    for(int i=1; i<=n; i++){
        cin>>a[i];
        c[a[i]]++;
        m = max(m, a[i]);
    }
    for(int i=1; i<=m; i++){
        for(int j=i; j<=m; j+=i){
            w[j] += c[i];
        }
    }
    for(int i=1; i<=n; i++)
        cout<<w[a[i]]-1<<"\n"; // 减去自己对自己的贡献
    return 0;
}

Divan and Kostomuksha (easy version)

考虑 \(dp_i\) 表示目前前缀 \(\gcd = i\),前缀 \(\gcd\) 和的最大值。

首先计算出序列 \(a\) 中是 \(i\) 的倍数的数量,记为 \(cnt_i\)。(这里处理方法和上一题相似

接下来考虑转移,假设是从 \(j\) 转移,转移到 \(i\),相当于在 \(j+1\) 至 \(f_j\) 所在位置全部填上 \(a\) 中是 \(i\) 的倍数但不是 \(j\) 的倍数的数,容易发现多的是 \(i\) 的倍数的数量为 \(cnt_i - cnt_j\)。所以转移式为:

\[dp_i = \max_{i \mid j}(dp_j + (cnt_i - cnt_j) \times i) \]

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 5e6;

ll cnt[N+5], a[N+5];
ll dp[N+5];
//cnt 表示 a 中能被 i 整除的数的个数
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    ll n, m = 0; cin>>n; // m 是值域
    for(int i=1; i<=n; i++){
        cin>>a[i];
        cnt[a[i]]++;
        m = max(m, a[i]);
    }
    for(int i=1; i<=m; i++){
        for(int j=i*2; j<=m; j+=i){
            cnt[i] += cnt[j];
        }
    }
    ll ans = 0;
    for(int i=m; i>=1; i--){
        dp[i] = cnt[i]*i;
        for(int j=i*2; j<=m; j+=i){
            dp[i] = max(dp[i], dp[j]+1ll*(cnt[i]-cnt[j])*i);
        }
        ans = max(ans, dp[i]);
    }
    cout<<ans;
    return 0;
}

标签:em,gcd,数论,hspace,sum,varphi,int,笔记,初等
From: https://www.cnblogs.com/FlyPancake/p/18306121

相关文章

  • 7月16日JavaSE学习笔记
    方法(函数、过程)语法返回值类型方法名(参数列表){方法体}返回值类型:该方法必须返回的一个这个类型的对象当方法不需要返回值时,返回值类型就定义为voidpublicstaticintmax(inta,intb){intmax=a>b?a:b;//方法名和变量名不会冲突//return返回......
  • 信创学习笔记(四),信创之数据库DB思维导图
    创作不易只因热爱!!热衷分享,一起成长!“你的鼓励就是我努力付出的动力”一.信创学习回顾1.信创内容信创内容思维导图2.信创之CPU芯片架构信创之CPU芯片架构思维导图3.信创之操作系统OS信创之操作系统OS思维导图二.信创之国产数据库DB思维导图用一张图学习......
  • 设计模式之简单工厂模式(学习笔记)
    定义简单工厂模式(SimpleFactoryPattern)是一种创建型设计模式,它定义一个用于创建对象的接口,但由一个单独的类来实现实际创建的工作。简单工厂模式通过在一个类中集中管理对象的创建过程,可以减少客户端与具体类之间的耦合,使得代码结构更加清晰和易于维护。通过专门定义一个类来负......
  • 中国白酒品鉴知识培训一-笔记
    一白酒文化白酒起源于农业 酒祖仪狄 杜康依傍长江而栖的四川、贵州,一直是我国白酒文化的重要发祥地和传承地。是舞动中国白酒金三角龙头,打造世界顶尖白酒的基地。我国是世界上最早酿酒的国家之一,对世界酿酒技术的发展作出了巨大贡献。酿造白酒离不开水、土、气、气、......
  • 点分树 学习笔记
    前言还真没有。点分树点分树是个神秘的东西。点分树是通过更改原树形态使树的层数变为稳定\(\logn\)的一种重构树。常用于解决与树原形态无关的带修改问题。是这样的,有些树上问题,看起来用别的树形结构做不了,点分治能做。但是它不仅多次询问(\(n\)同级),还带修,有时候甚至......
  • Java基础-学习笔记04
    04运算符进制1.运算符逻辑与&和短路与&&共同点:两个条件都为true时,结果为true,否则为false。&逻辑与:不管第一个条件是否为false,第二个条件都要判断;&&短路与:如果第一个条件判断为false,则第二个条件不会判断。逻辑或|和短路或||共同点:两个条件只要有一个成立,结果......
  • 第八天笔记(项目测试工具悟道使用)
    禅道一、禅道的介绍(1)定义禅道是一个项目管理工具,也是一个bug管理工具,还是一个用例管理工具。(2)作用:为了解决众多企业在管理中出现混乱,无序的现象,开发出来(3)来源:禅道属易软天川公司(4)禅道是集于产品管理,项目管理,测试管理于一身,同时包含事务管理,组织管理8众多功能,是中小企业管理......
  • 7/16 训练笔记
    闲话插,就硬插,插完就过了(P4781【模板】拉格朗日插值模板题,写拉格朗日插值即可。代码:#include<bits/stdc++.h>#defineintlonglong#definerep(i,l,r)for(inti=l;i<=r;i++)usingnamespacestd;constintmod=998244353;intx[2010],y[2010],n,k;int......
  • 低开开发笔记(八): 低代码编辑器实现撤销回退(命令模式,防抖处理)
    好家伙, 0.代码已开源https://github.com/Fattiger4399/ph_questionnaire-.git 1.事件触发我们先从事件的触发开始讲起大致上我们有两个思路可以选择1.监控用户行为2.监控数据变化 两种选择都会有较难处理的部分,这里我们先选第二个选项 关于监控数据,首......
  • 【Datawhale AI夏令营】 Task1 学习笔记
    目录一、baseline二、NLP模型自然语言处理的主要任务自然语言处理的技术和方法自然语言处理的应用自然语言处理的挑战 三、赛题理解 赛题背景赛事任务术语词典干预术语词典干预的主要特点术语词典干预的实施方法四、实操 步骤体会感想   学习目标:跑......