「学习笔记」初等数论

· · 个人记录

前言

更好的阅读体验?

前置知识(这个应该很显然):\operatorname{lcm}(a,b)=\dfrac{ab}{\gcd(a,b)}

线性筛素数

直接上代码。

const int MAXN=100000008;
bool np[MAXN];
vector<int> prm,pre;
void gg(const int N=100000000){
    pre.resize(N+1);
    for(int i=2;i<=N;i++){
        if(np[i]==false){
            prm.push_back(i);
            pre[i]=i;
        }
        for(auto j:prm) if(i*j<=N){
            int k=i*j;
            pre[k]=j;
            np[k]=true;
            if(i%j==0) break;
        }else break;
    }
}

例题:B3716 分解质因子 3

说明一下,prm 存了所有范围内的素数,pre 存了第 i 个数的最小质因子。

这个板子直接背过就好了。(其实是我懒得讲

不等式方程(exgcd)

形如 ax+by=c 的方程(a,b 为整数),方程有解当且仅当 \gcd(a,b) \mid c

欧几里得算法:\gcd(a,b)=\gcd(b,a \bmod b)

当 $a|b$ 时,$ax+by=\gcd(a,b)$ 解为 $\begin{cases}x=0\\y=1\end{cases} \begin{aligned}\because ax+by&=\gcd(a,b)\\&=\gcd(b,a \bmod b)\\&=bx_1+(a\bmod b)y_1\\&=bx_1+(a-\lfloor\dfrac{a}{b}\rfloor \times b)y_1\\&=bx_1+(a-\lfloor\frac{a}{b}\rfloor \times b)y_1\\&=ay_1+b(x_1-\lfloor\frac{a}{b}\rfloor \times y_1) \end{aligned} \therefore \begin{cases}x=y_1\\y=x_1-\lfloor \frac{a}{b} \rfloor \times y_1 \end{cases} $\color{white}{\text{公式对不齐好难受啊。QWQ}}

递归函数

void gg(int &x,int &y,int a,int b){
    if(a%b==0){
        x=0,y=1;
    }else{
        int xx,yy;
        gg(xx,yy,b,a%b);
        x=yy;y=xx-a/b*yy;
    }
}

裴蜀定理(写于 2023.4.14)

这个是后补的。QWQ

定义

a,b 是不全为零的整数,则存在整数 x,y , 使得 ax+by=\gcd(a,b)

然后就发现前面的式子求的就是裴蜀定理的式子。

然后写一个我自己发现的东西:(只不过我看过的文章都没讲过,可能是因为为太简单了

ax+by=c 的解,其中 a,b 为整数且 \gcd(a,b)|c

首先求出 ax+by=\gcd(a,b) 的解,设解为 \begin{cases}x=x'\\y=y'\end{cases}

再设 k=\dfrac{c}{\gcd(a,b)},则 ax+by=c 的解为 \begin{cases}x=kx'\\y=ky'\end{cases}

然后就没了。QWQ

进一步的结论可以去下面的链接。

裴蜀定理

同余

概念

对两个整数 ab,如果它们除以 d 的余数相同,则称它们 **模

$$a \equiv b\pmod d$$ ------------ ## 一个小知识 $$a-k \times b=r \to a \equiv r \pmod b$$ ($k$ 为任意整数) # 欧拉函数 $\varphi \leftarrow$ 就是这个玩意。 ## 定义 欧拉函数 $\varphi(n)$ 表示小于等于 $n$(其实等不等于 $n$ 无所谓,因为 $n$ 一定不与 $n$ 互质)且与 $n$ 互质的正整数(与 $n$ 互质即对于一个数 $i$,$\gcd(i,n)=1$ 或者说是 $n \perp i$)的个数。 特别的,$\varphi(1)=1$。 顺便提一嘴,$1$ 与任何数都**互质**。 ~~显然,~~ 对于一个正整数 $a$,$a$ 是质数当且仅当 $\varphi(a)=a-1$。 ## 性质 > 积性:如果对于任意两个正整数 $a,b$,如果 $\gcd(a,b)=1$(即 $a \perp b$),$\varphi(a \times b)=\varphi(a)\times\varphi(b)$。 > 欧拉反演:$\sum\limits_{d \mid n}\varphi(d)=n$。($\sum_{d \mid n}F(d)$ 表示把 $n$ 以内所有能整除 $n$ 的数 $d$ 带入 $F(d)$ 中求和) >性质三(很重要):对于任意质数 $p$,$\varphi(p^k)=p^k-p^{k-1}$。 性质三证明:小于等于 $p^k$ 的数有 $p^k$ 个,其中 $p,2\times p,3\times p...t\times p$ 与 $p$ 不互质,~~显然,~~ $t=p^{k-1}$,所以与 $p$ 不互质的数有 $p^{k-1}$ 个,那么与 $p$ 互质的数就有 $p^k-p^{k-1}$ 个,即 $\varphi(p^k)=p^k-p^{k-1}$。 ## 计算欧拉函数 单个欧拉函数:设 $n$ 的唯一分解式(就是质因数分解)为 $n=p_1^{k_1} \times p_2^{k_2} \times ... \times p_s^{k_s}$(~~显然,~~ $p_1$ 到 $p_s$ 均为质数,且 $p_1^{k_1},p_2^{k_2}...p_s^{k_s}$ 两两互质),则 $\varphi(n)=\prod\limits_{i=1}^s \varphi(p_i^{k_i})$,$\prod\limits_{i=1}^s \varphi(p_i^{k_i})$ 能用性质三求解(其实就是 $\varphi(n)=\prod\limits_{i=1}^s (p_i^{k_i}-p_i^{k_i-1})$)。 在代码实现中有一个更简单的式子: $$\varphi(n)=n \times \prod\limits_{i=1}^s(1 - \frac{1}{p_i})$$ 代码: ```cpp int phi(int x){ int num=x; for(int i=2;i*i<=x;i++){ if(x%i==0) num=num/i*(i-1); while(x%i==0) x/=i; } if(x>1) num=num/x*(x-1); return num; } ``` ## 线性筛欧拉函数 在线性筛素数的同时可以筛出欧拉函数,设 $p$ 是 $i$ 的最小质因子,分三种情况讨论: 1. $i$ 为质数,$\varphi(i)=i-1$。 2. $p$ 是 $\frac{i}{p}$ 的质因子,$\varphi(i)=\varphi(\frac{i}{p}) \times p$。 3. $p$ 与 $\frac{i}{p}$ 互质,$\varphi(i)=\varphi(\frac{i}{p}) \times \varphi(p)$。 证明: 1. ~~显然~~ 2. 设 $i=p^k \times q$,其中 $q$ 不含质因子 $p$。则 $\varphi(i)=\varphi(q) \times \varphi(p^k)=\varphi(q) \times (p^k-p^{k-1})=\varphi(q) \times p(p^{k-1}-p^{k-2})=\varphi(q) \times p \times \varphi(p_{k-1})=\varphi(\frac{i}{p}) \times p
  1. 根据积性可得。

例题:P2158 [SDOI2008] 仪仗队。

假如 C君 在仪仗队的左前方,坐标为 (0,0)

首先可以发现,对于一个坐标 (i,j)C君 能看到这个位置当且仅当 \gcd(i,j)=1,并且在与 C君 位置相交的对角线左右两边能看到的位置对称。

可以推个柿子

n\le 2 时:

ans=2\sum\limits^{n-1}_ {i=1}\sum\limits^{i-1}_ {j=1} [\gcd(i,j)=1] +3 ans=2\sum\limits^{n-1}_ {i=1} \varphi(i) +3

用线性筛欧拉函数即可,记得当 n=1 时特判一下。

代码:

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 40010
using namespace std;
int n;
int phi[MAXN];
bool np[MAXN];
vector<int> prm,pre;
long long ans;
void gg(){
    pre.resize(n+10);
    for(int i=2;i<=n;i++){
        if(np[i]==false){
            phi[i]=i-1;
            pre[i]=i;
            prm.push_back(i);
        }
        for(int j=0;j<prm.size();j++){
            if(i*prm[j]<=n){
                int k=i*prm[j];
                pre[k]=prm[j];
                np[k]=true;
                if(i%prm[j]==0){
                    phi[k]=phi[i]*prm[j];
                    break;
                }else{
                    phi[k]=phi[i]*phi[prm[j]];
                }
            }else break;
        }
    }
}
int main(){
    cin>>n;
    if(n==1) cout<<0;
    else{
        gg();
        for(int i=1;i<=n-1;i++) ans+=phi[i];
        cout<<(ans+1)*2+1;
    }
    return 0;
}

2023.9.9

P2398 GCD SUM

(GCDMAT - GCD OF MATRIX 双倍经验)

求:

\sum\limits_{i=1}^n\sum\limits_{j=1}^n \gcd(i,j)

运用欧拉反演,可得:

\sum\limits_{i=1}^n\sum\limits_{j=1}^n \sum\limits_{d|\gcd(i,j)} \varphi(d)

转换,得:

\sum\limits_{i=1}^n\sum\limits_{j=1}^n \sum\limits_{d=1} ^n\varphi(d)[d|i][d|j] \sum\limits_{d=1} ^n\varphi(d)\sum\limits_{i=1}^n[d|i]\sum\limits_{j=1}^n [d|j] \sum\limits_{d=1}^n\varphi(d)\lfloor\frac{n}{d}\rfloor^2

线性欧拉筛即可。(可以用数论分块,也可以不用,因为 n 范围太小了)

不用数论分块:

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 100010
#define int long long
using namespace std;
int ans;
int n,phi[MAXN];
vector<int> prm;
bool vis[MAXN];
void prime(){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]){
            prm.push_back(i);
            phi[i]=i-1;
        }
        for(int j=0;j<prm.size();j++){
            if(i*prm[j]<=n){
                int nem=i*prm[j];
                vis[nem]=true;
                if(i%prm[j]==0){
                    phi[nem]=phi[i]*prm[j];
                    break;
                }else{
                    phi[nem]=phi[i]*phi[prm[j]];
                }
            }else break;
        }
    }
}
signed main(){
    cin>>n;
    prime();
    for(int i=1;i<=n;i++){
        ans+=phi[i]*(n/i)*(n/i);
    }
    cout<<ans;
    return 0;
}

用数论分块:

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 100010
#define int long long
using namespace std;
int ans;
int n,sum[MAXN],phi[MAXN];
vector<int> prm;
bool vis[MAXN];
void prime(){
    sum[1]=phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]){
            prm.push_back(i);
            phi[i]=i-1;
        }
        for(int j=0;j<prm.size();j++){
            if(i*prm[j]<=n){
                int nem=i*prm[j];
                vis[nem]=true;
                if(i%prm[j]==0){
                    phi[nem]=phi[i]*prm[j];
                    break;
                }else{
                    phi[nem]=phi[i]*phi[prm[j]];
                }
            }else break;
        }
        sum[i]=sum[i-1]+phi[i];
    }
}
signed main(){
    cin>>n;
    prime();
    int r;
    for(int l=1;l<=n;l=r+1){
        int nem=n/l;
        r=n/nem;
        ans+=(sum[r]-sum[l-1])*(n/l)*(n/l);
    }
    cout<<ans;
    return 0;
}

P2303 [SDOI2012] Longge 的问题

手推的式子(很抽象)

\sum\limits_{i=1}^n \gcd(i, n) \sum\limits_{k|n}k\sum\limits_{i=1}^n [\gcd(i, n)=k] \sum\limits_{k|n}k\sum\limits_{i=1}^n [\gcd(\frac{i}{k}, \frac{n}{k})=1] \sum\limits_{k|n}k\sum\limits_{i=1}^{\frac{n}{k}} [\gcd(i, \frac{n}{k})=1] \sum\limits_{k|n}k \times \varphi(\frac{n}{k})

单次求欧拉函数即可。

#include<bits/stdc++.h>
#define XD 114514
#define int long long
using namespace std;
int n,ans;
int phi(int x){
    int num=x;
    for(int i=2;i*i<=x;i++){
        if(x%i==0) num=num/i*(i-1);
        while(x%i==0) x/=i;
    }
    if(x>1) num=num/x*(x-1);
    return num;
}
signed main(){
    cin>>n;
    for(int i=1;i*i<=n;i++){
        if(i*i==n) ans+=i*phi(n/i);
        else if(n%i==0) ans+=i*phi(n/i)+(n/i)*phi(i);
    }
    cout<<ans;
    return 0;
}

2023.9.12

UVA11417 GCD / UVA11424 GCD - Extreme (I) /P1390 公约数的和/ SP3871 GCDEX - GCD Extreme /UVA11426 拿行李(极限版) GCD - Extreme (II) (5 倍经验,按数据范围从小到大排序)

求:

\sum\limits_{i=1}^n\sum\limits_{j=i+1}^n\gcd(i,j)

转化为:

\sum\limits_{i=1}^n\sum\limits_{j=1}^{i-1}\gcd(i,j)

后面的直接套用上一道题的式子,可以提前线性筛欧拉函数,然后提前把答案都求出来,这样就可以把前两倍经验给切了。QWQ

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 200010
using namespace std;
int n;
bool vis[MAXN];
int phi[MAXN];
vector<int> prm;
void sieve(){
    phi[1]=1;
    for(int i=2;i<=2e5+1;i++){
        if(!vis[i]){
            prm.push_back(i);
            phi[i]=i-1;
        }
        for(int j=0;j<prm.size() and i*prm[j]<=2e5+1;j++){
            vis[i*prm[j]]=true;
            if(i%prm[j]==0){
                phi[i*prm[j]]=phi[i]*prm[j];
                break;
            }
            phi[i*prm[j]]=phi[i]*phi[prm[j]];
        }
    }
}
long long ans[MAXN],num;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    sieve();
    for(int i=1;i<=2e5+1;i++){
        for(int j=1;j*j<=i;j++){
            if(j*j==i) num+=j*phi[j];
            else if(i%j==0) num+=1ll*j*phi[i/j]+(i/j)*phi[j];
        }
        num-=i;
        ans[i]=num;
    }
    while(true){
        cin>>n;if(n==0) break;
        cout<<ans[n]<<"\n";
    }
    return 0;
}

时间复杂度预处理 O(n\sqrt{n}),查询 O(1)

考虑优化。

抽象的手推式子

\sum\limits_{i=1}^n\sum\limits_{j=1}^{i-1}\gcd(i,j) \sum\limits_{i=1}^n\sum\limits_{j=1}^{i}\gcd(i,j)-i \sum\limits_{i=1}^n\sum\limits_{k|i}k \times \varphi(\frac{i}{k})-\frac{(n+1)\times n}{2}

看左面的式子。

\sum\limits_{i=1}^n\sum\limits_{k=1}^nk \times \varphi(\frac{i}{k})[k|i] \sum\limits_{k=1}^nk\sum\limits_{i=1}^n\varphi(\frac{i}{k})[k|i] \sum\limits_{k=1}^nk\sum\limits_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\varphi(i)

还是预处理线性筛欧拉函数,查询直接数论分块即可。

在代码里,为防止爆 long long,我把要减的 \frac{(n+1)\times n}{2} 直接在数论分块中算了。QWQ

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 4000010
using namespace std;
int n;
bool vis[MAXN];
int phi[MAXN];
long long num[MAXN];
vector<int> prm;
void sieve(){
    phi[1]=1;num[1]=1;
    for(int i=2;i<=4e6;i++){
        if(!vis[i]){
            prm.push_back(i);
            phi[i]=i-1;
        }
        num[i]=num[i-1]+phi[i];
        for(int j=0;j<prm.size() and i*prm[j]<=4e6;j++){
            vis[i*prm[j]]=true;
            if(i%prm[j]==0){
                phi[i*prm[j]]=phi[i]*prm[j];
                break;
            }
            phi[i*prm[j]]=phi[i]*phi[prm[j]];
        }
    }
}
long long ans;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    sieve();
    while(true){
        ans=0;
        cin>>n;
        if(n==0) break;
        int r;
        for(int l=1;l<=n;l=r+1){
            r=n/(n/l);
            ans+=(1ll*(r+1)*r/2-1ll*(l-1)*l/2)*(1ll*num[n/l]-1);
        }
        cout<<ans<<"\n";
    }

    return 0;
}

说句闲话,这篇题解的式子(是我上面推的式子的方法)有点问题。QWQ

2023.10.3

膜运算

这是一个裸题,因为显然可以用欧几里得定理搞成原题。

而我才不会告诉你我想的有亿点复杂。

推式子。

2023.10.9

发现一个可能有用的等式:

\sum\limits_{i=1}^ni[\gcd(n,i)=1]=\frac{\varphi(n)n}{2}

欧拉定理

对任意正整数 a,m\gcd(a,m)=1,一定有:a^{\varphi(m)} \equiv 1 \pmod m

逆元

逆元补充 (by 2023.5.11)

定义

对于任意正整数 x 满足 x \times x^{-1} \equiv 1 \pmod p,则称 x^{-1}x 在模 p 同余下的逆元(其实就跟普通的 x \times x^{-1}=1 中的 x^{-1} 差不多,只不过这里的逆元是在模 p 同余下,而且在模 p 同余下x\times x^{-1}=1,然后就可以得出 x^k \times x^{-1} \equiv x^{k-1} \pmod p

为了方便理解,我就先把下文中所有的字母下面带 0 的都为这个字母代表的数在模某一个数同余下的逆元。(因为我在听课时老师是用 x^{-1} 表示 x 在模 p 同余下的逆元QWQ)

性质

逆元存在性定理:x 在模 p 同余下存在逆元当且仅当 \gcd(x,p)=1

推论:当且仅当模数 p 是质数时,[1, p − 1] 内所有整数都存在模 p 下的逆元。 0 没有逆元。

逆元唯一性定理:模 p 同余下,一个整数 x 的逆元若存在,则唯一。

定理:在模质数 p 同余下, [1, p − 1] 内所有整数的逆元互不相同。

定理:一个数的逆元的逆元等于它自身。

计算逆元

先上例题。

P1082 [NOIP2012 提高组] 同余方程

根据逆元定义可得这是一道求逆元板子题(显然)。

因为题目说输入数据保证一定有解,根据逆元存在性定理可得 \gcd(a,b)=1

开始求逆元喽。

a \times x \equiv 1 \pmod b \therefore a\times x =1 + b \times k \therefore a\times x + b \times k = 1

(由于 k 为任意整数,我们不知道它是正数还是负数,就把前面的正负号省略了)

然后可以发现,这不就是一个不定方程吗。(因为 a,b 为已知量,只需求 x

在前面我们知道 \gcd(a,b)=1,所以可以用递归求解即可。

最后记得把 x 取模到 [1,b-1] 的范围即可。QWQ

代码如下。

#include<bits/stdc++.h>
#define XD 114514
#define int long long
using namespace std;
inline void gg(int &x,int &y,int a,int b){
    if(a%b==0){
        x=0,y=1;
    }else{
        int xx,yy;
        gg(xx,yy,b,a%b);
        x=yy;
        y=xx-a/b*yy;
    }
}
signed main(){
    int a,b,x,y;
    cin>>a>>b;
    gg(x,y,a,b);
    cout<<(x%b+b)%b;
    return 0;
}

当然也可以像下面这样写。

#include<bits/stdc++.h>
#define XD 114514
#define int long long
using namespace std;
inline void gg(int &x,int &y,int a,int b){
    if(a%b==0){
        x=0,y=1;
        return;
    }
    gg(y,x,b,a%b);
    y-=a/b*x;
}
signed main(){
    int a,b,x,y;
    cin>>a>>b;
    gg(x,y,a,b);
    cout<<(x%b+b)%b;
    return 0;
}

补充

写于 2023.5.25

由于扩展中国剩余定理中有一些比较难懂的东西,结果发现是前面学的有问题,所以在这里来个补充。

先看一道题。(这是我自己出的QWQ)

ax \equiv b \pmod p

按上面的思路可得 ax+pk=b

由裴蜀定理可得,如果 \gcd(a,p) \nmid b 则无解。

\gcd(a,p)=g

用 exgcd 可求 au+pv=g

\therefore a\dfrac{bu}{g}+p\dfrac{bv}{g}=g\dfrac{b}{g}=b

x=\dfrac{bu}{g}k=\dfrac{bv}{g}

这说明 x_0\equiv \dfrac{bu}{g}\pmod pax\equiv b \pmod p 的解。

x_1ax\equiv b \pmod p 的其他解。

ax_1 \equiv ax_0 \pmod p

所以 p \mid ax_1-ax_0

这蕴含 \dfrac{p}{g} \mid \dfrac{a(x_1-x_0)}{g}

因为 \dfrac{p}{g}\dfrac{a}{g} 没有公约数。

所以 \dfrac{p}{g} \mid x_1-x_0

x_1=x_0+k\dfrac{p}{g}k \in \mathbb{Z}

而相差 p 的倍数的解被规定相同,所以恰好有 g 个不同的解。

即取 k=0,1,...,g-1

其实学完扩展中国剩余定理再回来看可以感觉到其实 ax+by=c 也不一定只有一个解或无解。(只不过在 OI 中我们只需要求差不多最小正整数解即可QWQ)

好像很显然的样子QWQ。

欧拉定理的继续

欧拉定理有一个前提条件就是 \gcd(a,m)=1,根据逆元存在性定理就可得出这个 a 在模 m 同余下一定有逆元。让我们推个式子。QWQ

a^{\varphi(m)} \equiv 1 \pmod m a^{\varphi(m)}\times a^{-1} \equiv a^{-1} \pmod m a^{\varphi(m)-1} \equiv a^{-1} \pmod m

然后我们就得到了另一个求逆元的方法。(虽然不是很好用)

但是当 m 是质数时,a^{\varphi(m)} \equiv 1 \pmod m 就可以转化成 a^{m-1} \equiv 1 \pmod m 于是费马小定理出现了

费马小定理

对任意整数 a 和质数 pa^{p-1} \equiv 1 \pmod p

根据式子可以推出:

a^{-1} \equiv a^{p-2} \pmod{p}

然后我们就又双叒叕得到了一个求逆元的方法(用快速幂求一下 a^{p-2} \bmod p 即可)。

上个题QWQ P2613 【模板】有理数取余

\frac{a}{b} \bmod 19260817 的值。

让我们算一算吧。

\frac{a}{b} \bmod 19260817 a \times b^{-1} \bmod 19260817

所以我们只需算出 b19260817 同余下的逆元。由于 19260817 为一个质数,用费马小定理求即可。

而无解的情况就是 b19260817 倍数,那么 b \bmod 19260817 一定等于 0

直接上代码。

#include<bits/stdc++.h>
#define XD 114514
#define mod 19260817
using namespace std;
inline long long read(){
    char ch=getchar(),h;long long w=0;
    while(ch>'9' or ch<'0') h=ch,ch=getchar();
    while(ch>='0' and ch<='9') w=(w*10+(ch-'0'))%mod,ch=getchar();
    if(h=='-') w=-w;
    return w;
}
long long a,b;
inline long long gg(long long x,long long k){
    //求 x 在模 mod 同余下的逆元,即求 x^(mod-2)%mod 的值
    //这里 k 就表示 mod-2 的值 
    long long ans=1;
    while(k){
        if(k&1){
            ans*=x;ans%=mod;
        }
        x*=x;x%=mod;
        k>>=1;
    }
    return ans;
}//快速幂是啥偶就不讲了QWQ 
int main(){
    a=read(),b=read();
    if(b==0) cout<<"Angry!\n";
    else cout<<a*gg(b,mod-2)%mod;
    return 0;
}

线性求逆元

inv_i 表示 i 的逆元,有递推式:

inv_i \equiv - \lfloor \frac{p}{i} \rfloor \times inv_{p \bmod i} \pmod p

边界为 inv_1 = 1

又要开始推式子了。

p=i \times k +r (r \in [1,n)) 0 \equiv i \times k + r \pmod p r \equiv -i \times k \pmod p r \times r^{-1} \times i^{-1} \equiv -i \times i^{-1} \times r^{-1} \times k \pmod p i^{-1} \equiv -k \times r^{-1} \pmod p inv_i \equiv -\lfloor \frac{p}{i} \rfloor \times r^{-1} \pmod p inv_i \equiv -\lfloor \frac{p}{i} \rfloor \times inv_{p \bmod i} \pmod p

再解释一下,我们刚开始设 p=i \times k +r,其中 r \in [1,n)显然,k=\lfloor \frac{p}{i} \rfloorr=p \bmod i,这样应该能看懂了吧。QWQ

P3811 【模板】乘法逆元

下面是代码。

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 3000010
using namespace std;
int n,p,inv[MAXN];
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>p;
    inv[1]=1;
    for(int i=2;i<=n;i++){
//      inv[i]=(1ll*-(p/i)*inv[p%i]%p+p)%p;
        inv[i]=1ll*(p-p/i)*inv[p%i]%p;//这两个算式等价,只不过这个少取模一次 
        //实测,在P3811中第一个用398ms,第二个用338ms
    }
    for(int i=1;i<=n;i++) cout<<inv[i]<<'\n';
    return 0;
}

说句闲话,我刚开始做这道题时一直 \color{black}{TLE} 了两个点,最后把换行符 endl 改为 '\n'\color{green}{AC} 了,这个故事告诉我们,珍爱生命,远离 endl

B3717 组合数问题

首先知道:

C^m_n=\frac{n!}{m!(n-m)!}

显然:

C^m_n \equiv \frac{n!}{m!(n-m)!} \pmod {998244353}

之后就把 998244353 当做 p 就行了不然打一个九位数太麻烦了

\frac{n!}{m!(n-m)!} \bmod p= n! \times (m!)^{-1} \times [(n-m)!]^{-1} \bmod p

然后求 (m!)^{-1}显然:

\times (m-1)^{-1} \times m^{-1} \pmod p

所以先线性求一遍逆元,然后预处理出所有阶乘的逆元即可。

上代码。

#include<bits/stdc++.h>
#define XD 114514

using namespace std;
int T,N;
const int p=998244353;
int ans;
int main(){
    ios::sync_with_stdio(false);
    cin>>T>>N;
    vector<int> inv(N+1),a(N+1),b(N+1);
    inv[1]=1;//inv_i 表示 i 的逆元 
    a[1]=1;//a_i 表示 i!的值 
    b[0]=b[1]=1;//b_i 表示 i! 的逆元的值 
    for(int i=2;i<=N;i++){
        inv[i]=1ll*(p-p/i)*inv[p%i]%p;
        a[i]=1ll*a[i-1]*i%p;
        b[i]=1ll*b[i-1]*inv[i]%p;
    }
    while(T--){
        int n,m,num=1;
        cin>>n>>m;
        num=1ll*a[n]*b[m]%p*b[n-m]%p;
        ans^=num;
    }
    cout<<ans;
    return 0;
}

威尔逊定理(on 2023.7.11)

定理

p 为素数,那么:

(p-1)! \equiv -1 \pmod p

证明

p=2 时,(p-1)! \equiv 1 \equiv -1 \pmod p

取一个大于 2 的质数 p,那么对于任意正整数 x1 \le x \le p-1),它一定有一个逆元,且这 p-1 个数的逆元不同。对于这些逆元,逆元是它本身的是 1p-1。我们可以把剩下的整数 2,3,4,...,p-1 分成 (p-3)/2 份,每一份的乘积都模 p1,这说明:

2\times3\times4\times...\times (p-2) \equiv 1 \pmod p

再在两边同时乘上 1p-1,得:

(p-1)! \equiv p-1 \equiv -1 \pmod p

证毕。

它的逆定理为:

正整数 p 是质数的充要条件为:

(p-1)! \equiv -1 \pmod p

威尔逊定理用的很少,不过扩展卢卡斯定理可能要用。

中国剩余定理(CRT)

它是用来解决一个问题的。

问题

给定 n 个线性同余方程:

\begin{cases} x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ ... \\ x \equiv a_n \pmod {m_n} \end{cases}

其中保证 m_1,m_2,...,m_n 两两互质。求 x 的通解。

定理

上面的方程组有解,且按如下方式构造:

M=\prod\limits^n_{i=1}m_iM_i=\frac{M}{m_i}t_iM_i 在模 m_i 同余下的逆元。

则方程组的唯一通解是:

x \equiv \sum\limits^n_{i=1}a_i t_i M_i \pmod M

证明

显然:

\gcd(M_i,m_i)=1

且,

M_it_i \equiv 1 \pmod {m_i}

(能够保证 M_i 在模 m_i 同余下一定有逆元)

\therefore a_iM_it_i \equiv a_i \pmod{m_i}

另外,对于一个 jj \ne i),由于 m_i | M_j,于是 M_j \equiv 0 \pmod{m_i}

\therefore a_iM_it_i+m_jM_jt_j \equiv a_i \pmod{m_i} \therefore a_iM_it_i + \sum\limits^n_{j=1,i\ne j} a_jM_jt_j \equiv a_i \pmod{m_i} \sum\limits^n_{i=1} a_iM_it_i \equiv a_i \pmod{m_i}

这就验证了 x=\sum\limits^n_{i=1} a_iM_it_i 是方程组的一组特解。

运用小学知识(其实就是我懒得证明)可得每两组相邻特解之间相差为 M 的倍数,于是 x+kM 也是一个特解,由此,通解唯一性得证。

例题

P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪

模板题,直接上代码。

#include<bits/stdc++.h>
#define XD 114514
#define MAXN 20
#define int long long
using namespace std;
int n;
void gg(int &x,int &y,int a,int b){//求逆元
    if(a%b==0){
        x=0,y=1;
        return;
    }
    gg(y,x,b,a%b);
    y-=a/b*x;
}
int a[MAXN],b[MAXN],M[MAXN],m=1,t[MAXN],ans;
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i]>>b[i];//x=ai*k+bi -> x mod ai=bi mod ai
        m*=a[i];
    }
    for(int i=1;i<=n;i++){
        M[i]=m/a[i];
        int x=0,y=0;
        gg(x,y,M[i],a[i]);
        t[i]=(x%a[i]+a[i])%a[i];
    }
    for(int i=1;i<=n;i++){
        ans+=b[i]*t[i]%m*M[i]%m;
        ans%=m;
    }
    cout<<ans;
    return 0;
}

P3868 [TJOI2009] 猜数字

b | (n-a_i) n-a_i \equiv 0 \pmod{b_i} n \equiv a_i \pmod{b_i}

然后套模板即可。

拓展中国剩余定理

等我学完再写。QWQ

终于学完力。QWQ(on 2023.5.23)

\begin{cases} x \equiv a_1 \pmod {b_1} \\ x \equiv a_2 \pmod {b_2} \\ ... \\ x \equiv a_n \pmod {b_n} \end{cases} 首先拿出前两个方程。 $$\begin{cases} x \equiv a_1 \pmod {b_1} \\ x \equiv a_2 \pmod {b_2} \end{cases}$$ 可变为 $$\begin{cases} x = b_1k_1+a_1 \\ x = b_2k_2+a_2 \end{cases}$$ ($k_1,k_2$ 为任意整数) 所以 $$b_1k_1+a_1=b_2k_2+a_2$$ 可变为 $$b_1k_1+b_2k_2=a_2-a_1$$ (这里 $k_2$ 不是 $-k_2$ 的解释和在用 `exgcd` 求逆元中 $k$ 的解释相同) 设 $g=\gcd(b_1,b_2)

就可以用 exgcd 求出 b_1h_1+b_2h_2=g 的解。

当然如果题目中不保证一定有解到话判断 $(a_2-a_1)$ 是否能被 $g$ 整除即可。如果 $g \nmid (a_2-a_1)$ 则无解。 求出 $h_1$ 和 $h_2$ 后可求出 $k_1=\frac{a_2-a_1}{g}h_1$。 从而得到一组通解。 $$k_1=k_1+\frac{b_2}{g}t$$ $$k_2=k_2-\frac{b_1}{g}t$$ ($t$ 为任意整数) 将 $k_1$ 带入 $x = b_1k_1+a_1$ 中。 $$x=b_1k_1+\frac{b_1b_2}{g}t+a_1$$ 所以 $$x \equiv b_1k_1+a_1 \pmod {\operatorname{lcm}(b_1,b_2)}$$ 之后继续合并即可。 例题:[P4777 【模板】扩展中国剩余定理(EXCRT) ](https://www.luogu.com.cn/problem/P4777) ```cpp #include<bits/stdc++.h> #define XD 114514 #define MAXN 100010 #define ll long long #define int __int128 using namespace std; int n,a[MAXN],b[MAXN]; int gcd(int x,int y){ return y==0?x:gcd(y,x%y); } int lcm(int x,int y){ return x/gcd(x,y)*y; } void exgcd(ll a,ll b,int &x,int &y){ if(a%b==0){ x=0,y=1; return; } exgcd(b,a%b,y,x); y-=a/b*x; } ll cab_a,cab_b; int k1,k2,g,nem1,nem2,mod; void solve(int a,int b){ g=gcd(cab_b,b); if((a-cab_a)%g!=0) exit(0);//无解 exgcd(cab_b,b,nem1,nem2); k1=(a-cab_a)/g*nem1; k1=(k1%(b/g)+(b/g))%(b/g);//将 k1 变成正整数 mod=lcm(cab_b,b); cab_a=((cab_b*k1+cab_a)%mod+mod)%mod;//将 cab_a 变成正整数 cab_b=mod; } signed main(){ cin>>n; for(int i=1;i<=n;i++) cin>>b[i]>>a[i]; cab_a=a[1],cab_b=b[1]; for(int i=2;i<=n;i++){ solve(a[i],b[i]); } cout<<cab_a%cab_b; return 0; } ``` 再来解释一下,其实 $ax+by=\gcd(a,b)$ 如果有解则解有无数个,它的通解为: $$\begin{cases} x = x + k\dfrac{b}{\gcd(a,b)} \\ y = y-k\dfrac{a}{\gcd(a,b)} \end{cases}$$ ($k$ 为任意整数) 把新的 $x,y$ 带入 $ax+by=\gcd(a,b)$ 是可以发现方程还是成立的。 由于题目中要求的是**最小非负整数解**,所以在代码中要将变量变为正整数。 # 容斥原理 关于集合的概念看 [这里](https://www.luogu.com.cn/problem/B3633)。 ~~说句闲话,我开始了解集合也是开始于上面的链接。~~ ------------ 我们有 $A,B,C$ 三个集合,求 $|A \cup B \cup C|$ ,可以列出式子: $$|A \cup B \cup C|=|A|+|B|+|C|-|A \cap B|-|A \cap C|-|B \cap C|+|A \cap B \cap C|$$ 如果我们有 $n$ 个集合 $P_1,P_2,...,P_n$,则: $$|\bigcup\limits_{i=1}^n P_i|=\sum\limits_{S\subseteq[1,2,...,n]}(-1)^{|S|-1}|\bigcap\limits_{s\in S}P _ s|$$ # 二项式定理(写于 2023.4.29) 这个主要说的就是 $\dbinom{n}{m}$ 这东西。 由于这个可能要写的很多,所以我把它放在一篇单独的博客上。 [二项式定理](https://www.luogu.com.cn/blog/defineXD114514/er-xiang-shi-ji-shuo) # Lucas定理 ## 定义 对于一个质数 $p$, $$\dbinom{n}{m} \equiv \dbinom{\lfloor \frac{n}{p} \rfloor}{\lfloor \frac{m}{p} \rfloor}\dbinom{n \bmod p}{m \bmod p}\pmod p$$ 证明我暂时不会,先放个链接QWQ。 [链接](https://oi-wiki.org/math/number-theory/lucas/) 但是现在会了。QWQ(by 2023.7.11) ~~建议别看 OI-wiki 上的证明,反正我没看懂。QWQ~~ ## 证明 在证明之前,要证明一个式子。 $$(1+x)^p \equiv 1+x^p \pmod p$$ (其中 $p$ 为质数) ### 证明这个式子 首先知道 $(1+x)^p=\sum\limits_{k=0}^p \dbinom{p}{k}x^k$。 对于 $\sum\limits_{k=0}^{p} \dbinom{p}{k}$,由于知道 $p$ 为质数,所以这里的 $\dbinom{p}{k}$ 一定是 $p$ 的倍数,即 $\dbinom{p}{k} \equiv 0 \pmod p$,因为 $\dbinom{p}{k}=\dfrac{p!}{k!(p-k)!}$,而想要在 $\dfrac{p!}{k!(p-k)!}$ 消掉 $p$ 只能是 $k=p$ 或 $p-k=p$,即 $k=p$ 或 $0$ ,而对于其他的,分子中有 $p$ 这个因数,并且 $p$ 是质数,所以只有分母有 $p$ 这个因数才行。 所以我们可以得出: $$(1+x)^p \equiv 1 +\sum\limits_{k=1}^{p-1}\dbinom{p}{k}x^k+x^p \equiv 1+x^p \mod p$$ 证毕。 ------------ 现在开始正式证明卢卡斯定理。 先设 $n=k_np+r_n$,$m=k_mp+r_m$,所以 $\lfloor \dfrac{n}{p}\rfloor=k_n$,$\lfloor \dfrac{m}{p}\rfloor=k_m$,$n \bmod p=r_n$,$m \bmod p=r_m$。 $$\begin{aligned}(1+x)^n&=(1+x)^{k_np+r_n}\\&=(1+x)^{k_np}\times (1+x)^{r_n}\\&=((1+x)^p)^{k_n}\times (1+x)^{r_n}\\&\equiv (1+x^p)^{k_n}\times (1+x)^{r_n} \pmod p\\&\equiv \sum\limits_{i=0}^{k_n}\dbinom{k_n}{i}x^{ip}\times \sum\limits_{i=0}^{r_n}\dbinom{r_n}{i}x^i \pmod p\end{aligned}$$ 那就有: $$\sum\limits_{i=0}^n\dbinom{n}{i}x^i\equiv \sum\limits_{i=0}^{k_n}\dbinom{k_n}{i}x^{ip}\times \sum\limits_{i=0}^{r_n}\dbinom{r_n}{i}x^i \pmod p$$ 对于任意一个整数 $z$,必定有一组 $i,j$ 满足 $x^z=x^{ip}x^j$。所以 $z=ip+j$,那就是说上面的式子中 $i$ 取任意一个,右面都有一个新的跟它恒等(应该是在模 $p$ 意义下)。 当上面的式子左面的 $i=m$ 时,式子右面则分别 $i=k_m=\lfloor\dfrac{m}{p}\rfloor$,$i=r_m=m \bmod p$。 则: $$\begin{aligned}\dbinom{n}{m}x^m &\equiv\dbinom{k_n}{k_m}x^{k_mp}\times \dbinom{r_n}{r_m}x^{r_m}\pmod p \\&\equiv\dbinom{k_n}{k_m}\dbinom{r_n}{r_m}x^{k_mp}x^{r_m}\pmod p \\&\equiv\dbinom{k_n}{k_m}\dbinom{r_n}{r_m}x^{k_mp+r_m}\pmod p \\&\equiv\dbinom{k_n}{k_m}\dbinom{r_n}{r_m}x^m\pmod p \end{aligned}$$ 两边同乘 $x^m$ 的逆元,便可得出: $$\dbinom{n}{m} \equiv \dbinom{k_n}{k_m}\dbinom{r_n}{r_m}\pmod p$$ 即: $$\dbinom{n}{m} \equiv \dbinom{\lfloor \frac{n}{p} \rfloor}{\lfloor \frac{m}{p} \rfloor}\dbinom{n \bmod p}{m \bmod p}\pmod p$$ 证毕。 ------------ 当 $p$ 不为质数时,就需要用到 exLucas 定理了。 exLucas 定理之后再学。QWQ ## 例题 [P3807 【模板】卢卡斯定理/Lucas 定理](https://www.luogu.com.cn/problem/P3807) 模板题。~~听君一席话,如听一席话。~~ 直接上代码。 ```cpp #include<bits/stdc++.h> #define XD 114514 using namespace std; int t; int qpow(int x,int y,int p){//快速幂 int ans=1; while(y){ if(y&1) ans=1ll*ans*x%p; x=1ll*x*x%p; y>>=1; } return ans; } int C(int n,int m,int p){//组合数 if(n<m) return 0; int a=1,b=1; for(int i=1;i<=n;i++) a=1ll*a*i%p; for(int i=1;i<=m;i++) b=1ll*b*i%p; for(int i=1;i<=n-m;i++) b=1ll*b*i%p; return 1ll*a*qpow(b,p-2,p)%p; } int Lucas(int n,int m,int p){//Lucas 递推 if(n<p and m<p) return C(n,m,p); return 1ll*Lucas(n/p,m/p,p)*C(n%p,m%p,p)%p; } int main(){ cin>>t; while(t--){ int n,m,p; cin>>n>>m>>p; cout<<Lucas(n+m,m,p)<<'\n'; } return 0; } ``` # 其他(写于 2023.5.23) 这里是其他的一些数论题的题解(算是吧)。 [P3708 koishi的数学题 ](https://www.luogu.com.cn/problem/P3708) 很有意思的一道题。 首先我看到这道题后思考如何由 $f(x)$ 转变到 $f(x+1)$。(时间复杂度最好为 $O(1)$) $$f(x)=\sum\limits_{i=1}^n x \bmod i$$ $$f(x)=\sum\limits_{i=1}^n x-\lfloor\frac{x}{i}\rfloor i$$ $$f(x)=nx - \sum\limits_{i=1}^n \lfloor\frac{x}{i}\rfloor i$$ 那么: $$f(x+1)=n(x+1) - \sum\limits_{i=1}^n \lfloor\frac{x+1}{i}\rfloor i$$ $$f(x+1)=n+nx - \sum\limits_{i=1}^n \lfloor\frac{x+1}{i}\rfloor i$$ 思考从 $\sum\limits^{i=1}_n \lfloor\frac{x}{i}\rfloor i$ 到 $\sum\limits^{i=1}_n \lfloor\frac{x+1}{i}\rfloor i$ 数值的变化。 可以发现,只有 $i | (x+1)$,从 $\lfloor\frac{x}{i}\rfloor$ 到 $\lfloor\frac{x+1}{i}\rfloor$ 的值会加 $1$,那么总共的值会加 $i$。 所以总共增加的值为 $x+1$ 的因数之和。 所以 $f(x+1)=f(x) + n - a_{x+1}$ ($a_{x+1}$ 是 $x+1$ 的因数之和) 而且 $f(1) = n-1$。(~~显然~~) ```cpp #include<bits/stdc++.h> #define XD 114514 #define MAXN 1000010 using namespace std; int n,a[MAXN]; long long ans; void cab(){//预处理 for(int i=1;i<=n;i++){ for(int j=i;j<=n;j+=i){ a[j]+=i; } } } void solve(){//解决问题 ans=n-1; cout<<ans<<" "; for(int i=2;i<=n;i++){ ans=ans+n-a[i]; cout<<ans<<" "; } } int main(){ cin>>n; cab(); solve(); return 0; } ``` ------------ on 2023.8.12。 [P2568 GCD](https://www.luogu.com.cn/problem/P2568) 就是求 $\large\sum\limits_{p \in \text{prime}}\sum\limits_{i=1}^n\sum\limits_{j=1}^n[\gcd(i,j)==p]$。由 $\gcd(i,j)=p$ 可得 $\gcd(\dfrac{i}{p},\dfrac{j}{p})=1$,所以说原式可以变成 $\large\sum\limits_{p \in \text{prime}}\sum\limits_{i=1}^{\frac{n}{p}}\sum\limits_{j=1}^{\frac{n}{p}}[\gcd(i,j)==1]$,不难发现 $\sum\limits_{i=1}^{\frac{n}{p}}\sum\limits_{j=1}^{\frac{n}{p}}[\gcd(i,j)==1]$ 和 [P2158 [SDOI2008] 仪仗队](https://www.luogu.com.cn/problem/P2158) 中的式子很像,所以直接线性求欧拉函数即可。 ```cpp #include<bits/stdc++.h> #define XD 114514 #define MAXN 10000010 #define int long long using namespace std; int n; int sum[MAXN],phi[MAXN],tot,prime[MAXN]; bool vis[MAXN]; long long ans; void solve(){ phi[1]=1; for(int i=2;i<=n;i++){ if(!vis[i]) prime[++tot]=i,phi[i]=i-1; for(int j=1;j<=tot and i*prime[j]<=n;j++){ vis[i*prime[j]]=true; if(i%prime[j]==0){ phi[i*prime[j]]=phi[i]*prime[j]; break; }else{ phi[i*prime[j]]=phi[i]*phi[prime[j]]; } } } for(int i=1;i<=n;i++) sum[i]=sum[i-1]+phi[i]; } signed main(){ cin>>n; solve(); for(int i=1;i<=tot;i++) ans+=2*sum[n/prime[i]]-1; cout<<ans; return 0; } ```