基本数论下

一.扩展欧几里得

扩展欧几里得做了一件什么事情呢,实际上对于下面那么一个问题

扩展欧几里得会求得一组解x,y使得等式成立;

证明如下:

首先由裴蜀定理可知,一定存在一组解使得等式成立;

其次根据辗转相除法可以递推出:

代码如下:

#include<iostream>
using namespace std;


void exgcd(int a, int b, int &x, int &y){
    if(b==0){
        x=1;
        y=0;
        return ;
        
    }
    
    int x1,y1;
    exgcd(b,a%b,x1,y1);
    x=y1;
    y=x1-a/b*y1;
    return ;
    
}
int main(){
    int n;
    cin>>n;
    while(n--){
        int a,b,x,y;
        cin>>a>>b;
        exgcd(a,b,x,y);
        cout<<x<<' '<<y<<endl;
    }
}

下面还是看一道扩展欧几里得的题目:

我们做一下变形,我们可以知道

ax-b=km,那么ax-km=b,将y=-k,那么ax+my=b;

如果等式成立,那么b一定是gcd(a,m)的倍数。否则一定不存在解

那么一看,这不就是模板吗,直接用即可。

二.中国剩余定理

实际上中国剩余定理描述了这么一个事情,

注意到,这里要求模数必须是两两互质的,不要求两两互质的是扩展中国剩余定理;

在解决中国剩余定理前,我们先了解两种求逆元的方法:

1.快速幂求逆元(要求模数为质数)

显然对于ax=1(mod p)

ax=a^{p-1}(mod p)

于是x=a^{p-2}(mod p);

2.exgcd求逆元(要求gcd(a,p)=1)

显然对于ax=1(mod p)

ax-kp=1

令y=-k

那么ax+py=1,利用扩展欧几里得算法可求出逆元x

了解了以上两个求逆元后,我们可以开始做中国剩余定理了

算法步骤:

题目:【模板】中国剩余定理(CRT)/ 曹冲养猪 - 洛谷

代码:注意不可使用快速幂求逆元,因为本题只是说两两互质,并不是说是质数,同时本题需要开int128才能过hack1

#include<bits/stdc++.h>
using namespace std;
#define int long long
using i128 = __int128_t;

std::ostream &operator<<(std::ostream &os, i128 n) {
    std::string s;
    while (n) {
        s += '0' + n % 10;
        n /= 10;
    }
    std::reverse(s.begin(), s.end());
    return os << s;
}

inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = (x << 1ll) + (x << 3ll) + (c - '0');
        c = getchar();
    }
    return x * f;
}


signed main() {

    auto exgcd = [&](auto self, i128 a, i128 b, i128& x, i128& y) ->i128 {
        if (b == 0ll) {
            x = 1ll; y = 0ll;
            return a;
        }
        i128 r = self(self, b, a % b, x, y);
        i128 temp = x; x = y; y = temp - (a / b) * y;
        return r;
        };

    int n = read();

    vector<i128> a(n), p(n);
    for (int i = 0; i < n; i++) {
        p[i] = read();
        a[i] = read();
    }

    auto CRT = [&]() -> i128 {
        i128 M = 1ll;
        for (int i = 0; i < n; i++) {
            M = M * p[i];
        }
        i128 ans = 0;
        for (int i = 0; i < n; i++) {
            i128 minv = M / p[i];
            i128 v = 0, k = 0;
            exgcd(exgcd, minv, (i128)p[i], v, k);
            v = (v % p[i] + p[i]) % p[i];
            ans = (ans + (minv * a[i] % M) * v % M) % M;
        }
        return ans;
    };

    cout << CRT() << endl;
    return 0;
}

下面看一题扩展中国定理(不要求两两互质)

【模板】扩展中国剩余定理(EXCRT) - 洛谷

大致思路是:先对两组数据求解,依次进行;

同时

这里我只对为什么通解是lcm(a1,a2)做解释,这是由于x同余m1(mod a1),那么也就是说求出了x的特解后,通解加上的数必然是a1的倍数,其次对于x同余m2(mod a2);那么通解加上的数必然是a2的倍数;综合来看最小的倍数就是(a1,a2)的最小公倍数。

代码如下:注意开int128

#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int N = 1e5 + 10;
ll x, y, d; int n;

void exgcd(ll &x, ll &y, ll a, ll b) {
    if(!b) d = a, x = 1, y = 0;
    else exgcd(y, x, b, a % b), y -= a / b * x;
}

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

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

ll a, b, A, B;

void merge() {
    exgcd(x, y, a, A);
    ll c = B - b;
    if(c % d) puts("-1"), exit(0);
    x = x * c / d % (A / d);
    if(x < 0) x += A / d;
    ll mod = lcm(a, A);
    b = (a * x + b) % mod; if(b < 0) b += mod;
    a = mod;
}

int main() {
    scanf("%d", &n);
    for(int i = 1 ; i <= n ; ++ i) {
        long long _A, _B;
        scanf("%lld%lld", &_A, &_B), A = _A, B = _B;
        if(i > 1) merge();
        else a = A, b = B;
    }
    printf("%lld\n", (long long)(b % a));
}

三.组合数

组合数的求法:

1.递推式

2.逆元(注意快速幂求逆元的时候需要注意模数需要为质数)

3.lucas(要求模数为质数,实际上可以证明出来)

这里给出证明的链接卢卡斯定理 - OI Wiki

代码模板:

#include<iostream>
using namespace std;

typedef long long LL;

int qmi(int a, int b, int p){
    int res=1;
    while(b){
        if(b&1)res=(LL)res*a%p;
        b>>=1;
        a=(LL)a*a%p;
    }
    return res;
}

LL C(int a, int b, int p){
    if(b>a)return 0;
    LL res=1;
    for(int i=1,j=a; i<=b; i++,j-- ){
        res=res*j%p;
        res=res*qmi(i,p-2,p)%p;
    }
    return res;
}

LL lucas(LL a,LL b, LL p){
    if(a<p && b<p)return C(a,b,p);
    return C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}
int main(){
    int n;
    cin>>n;
    while(n--){
        LL a,b,p;
        cin>>a>>b>>p;
        cout<<lucas(a,b,p)<<endl;
    }
}

4.高精度暴力

采用质数分解,由于组合数一定为整数

代码:

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;
int primes[5010];
bool st[5010];
int cnt=0;
int sum[5010];
void get_primes(int a){
    for(int i=2; i<=a; i++){
        if(!st[i]){
            primes[cnt++]=i;
            st[i]=true;
        }
        for(int j=0; i*primes[j]<=a && j<cnt; j++){
            st[i*primes[j]]=true;
            if(i%primes[j]==0)break;
        }
    }
}

vector<int> mul(vector<int>v, int b){
    vector<int>c;
    int len=v.size();
    int t=0;
    for(int i=0; i<len; i++){
        t+=b*v[i];
        c.push_back(t%10);
        t/=10;
    }
    while(t){
        c.push_back(t%10);
        t/=10;
    }
    return c;
}

int get(int a, int p){
    int res=0;
    while(a){
        res+=a/p;
        a/=p;
    }
    return res;
}
int main(){
    int a,b;
    cin>>a>>b;
    get_primes(a);

    for(int i=0; i<cnt; i++){
        sum[i]=get(a,primes[i])-get(b,primes[i])-get(a-b,primes[i]);
        //cout<<primes[i]<<' '<<sum[i]<<endl;
    }

    vector<int>v;
    v.push_back(1);
    for(int i=0; i<cnt; i++){
        for(int j=0; j<sum[i]; j++){
            v=mul(v,primes[i]);
        }
    }
    for(int i=v.size()-1; i>=0; i--)cout<<v[i];
    cout<<endl;
    return 0;
}

卡特兰数:

采用对称的方法证明,如果有一条边跨过一部分最终去到(n,n),那么一定可以通过对称的方式去到(n-1,n+1);

上述第一点是显而易见的,那么对于任意一条从(0,0)->(n-1,n+1)的路径是否一定经过红线呢,

下面给出理论证明:

实际上对于任何(0,0)->(n-1,n+1)的路径path我们可以知道,一定存在某一时path一定穿过红线,那么记下这个时刻点,这样我们一定可以跟这个时刻点,后面path的方式,唱“反调”,当其到达(n-1,n+1)时,我们到达(n,n);

四.高斯消元

这个直接套模板即可,基本的思想是消元;

步骤如下:

1.找到一行第i列不为0的行r,将其移到第i行(如果找不到就continue)

2.进行消元,依次类推

3.回代求解

题目链接:【模板】高斯消元法 - 洛谷

时间复杂度O(n^3)

注意异或的情况;

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 110;
const double eps = 1e-8;

int n;
double a[N][N];

int gauss()  // 高斯消元,答案存于a[i][n]中,0 <= i < n
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )  // 找绝对值最大的行
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

        if (fabs(a[t][c]) < eps) continue;

        for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]);  // 将绝对值最大的行换到最顶端
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];  // 将当前行的首位变成1
        for (int i = r + 1; i < n; i ++ )  // 用当前行将下面所有的列消成0
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];

        r ++ ;
    }

    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2; // 无解
        return 1; // 有无穷多组解
    }

    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[i][j] * a[j][n];

    return 0; // 有唯一解
}


int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n + 1; j ++ )
            scanf("%lf", &a[i][j]);

    int t = gauss();
    if (t == 2 || t==1) puts("No Solution");
    else
    {
        for (int i = 0; i < n; i ++ )
            printf("%.2lf\n", a[i][n]);
    }

    return 0;
}

五.容斥原理和博弈论

这两块内容,实际上不好阐述;这里给出要点,读者可以在数学部分简介 - OI Wiki中仔细学习;

等后面有空了我再加上这两部分的详细阐述;

容斥原理的要点为:质数筛,通过+1,-1,+1,-1来实现不重不漏的求解

博弈论的要点是:异或+seg函数,本质上来说是根据游戏规则推出seg函数(采用记忆化搜索);

扩展:还有一系列的比如,扩展欧拉定理,扩展kmp,扩展卢卡斯定理,扩展中国剩余定理;

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值