数论快速入门(同余、扩展欧几里德、中国剩余定理、大素数测定和整数分解、素数三种筛法、欧拉函数以及各种模板)

这篇博客介绍了数论的基础概念,包括同余定理、扩展欧几里德算法及其应用,以及中国剩余定理。通过简单的例子和AC代码展示了如何使用这些算法解决实际问题,适合初学者入门。
摘要由CSDN通过智能技术生成

数学渣渣愉快的玩了一把数论,来总结一下几种常用的算法入门,不过鶸也是刚刚入门,

 所以也只是粗略的记录下原理,贴下模板,以及入门题目(感受下模板怎么用的)

PS:文中亮色字体都可以点进去查看百度原文

附赠数论入门训练专题:点我打开专题(题目顺序基本正常,用以配套数论入门)

一、同余定理

同余式 : a ≡ b (mod m) (即 a%m == b%m)

简单粗暴的说就是:若 a-b == m 那么 a%m == b%m

这个模运算性质一眼看出。。。直接上入门水题:

Reduced ID Numbers

附AC代码(这个也没啥模板。。。。知道就好)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stdlib.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
/*
同余:
if
    a-b == m
then
    a%m == b%m

*/
const int N = 1000000;
bool vis[N+5];
int a[N+5];
bool ol[N+5];
int main()
{
    int T;cin>>T;
    while (T--)
    {
        int n;
        scanf("%d",&n);
        mem(vis,0);
        for (int i = 1;i <= n;++i) scanf("%d",a+i);
        for (int i = 1;i <= n;++i)
        {
            for (int j = i+1;j <= n;++j)
            {
                int d = abs(a[i]-a[j]);
                vis[d] = 1;
            }
        }
        bool fd = 0;
        for (int m = 1;!fd;++m)
        {
            if (!vis[m])
            {
                mem(ol,0);bool ok = 1;
                for (int i = 1;ok&&i <= n;++i)
                {
                    if (ol[a[i]%m]) ok = 0;
                    else ol[a[i]%m] = 1;
                }
                if (ok)
                {
                    printf("%d\n",m);
                    fd = 1;
                }
            }
        }
    }
    return 0;
}

二、扩展欧几里德算法

这个扩展是从原欧几里德算法扩展而来,这个算法真心非常有用!非常有用!非常有用!(中国剩余定理也要用到它)

首先说欧几里德算法(其实就是我们小时候数学课上就学过的辗转相除法求gcd)

欧几里德说:gcd(a,b) = gcd(b,a%b)

于是得到欧几里德算法:

int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}
该算法可以在几乎是log的时间算a,b的最大公因数gcd

PS:__gcd(a,b)库函数可以直接调用,但是有些OJ上提交会CE

现在,我们令a,b的最大公因数为gcd,那么我们一定可以找到一组x,y满足:(这个是欧几里德的定理一)

a*x + b*y = gcd;

此时,我们设x0,y0是上述不定方程的特解,那么就可以用x0,y0表示出整个不定方程的通解

x = x0 + (b/gcd)*t;
y = y0 - (a/gcd)*t;

然后是怎么求解通解x,y?

倒过去看看欧几里德算法,显然,它的结束条件是 b = 0的时候返回a

这就意味着,终止状态是 :a = gcd ,b = 0;

将这组a,b代会不定方程ax+by=gcd,可以得到一组特解:x = 1,y = 0

找到终止状态,我们再看看递归的过程:

gcd = b*x1 + (a%b)*y1 
    = b*x1 + (a-(a/b)*b)*y1 // a%b = a-(a/b)*b
    = b*x1 + a*y1 - (a/b)*b*y1
    = a*y1 + b*(x1-a/b*y1)

最后得到gcd = a*y1 + b*(x1-a/b*y1)和原来的式子gcd = a*x + b*y一对比就得到:

x = y1,y = x1 - a/b*y1;

上面两个等式很重要!!(因为模板里面会直接用到,如果是直接背模板就得把它背熟)

下面就是扩展欧几里德算法:

int e_gcd(int a,int b,int &x,int &y)
{
    if (b == 0)
    {
        x = 1,y = 0;
        return a;
    }
    int ans = e_gcd(b,a%b,x,y);
    int tmp = x;
    x = y;
    y = tmp - a/b*y;
}

这个算法本质上还是求a,b的最大公因数,只是在计算的过程中顺带计算x,y(同时体验了一把引用的神奇)

扩展欧几里德算法有什么用?反正用法很多,它可以求解形如 ax+by=c的通解

但是实际应用中通常指要求在通解中选一些特殊的解,

比如一个数对于另一个数的乘法逆元。那么什么是乘法逆元?

ax ≡ 1(mod b) // ax % b = 1
这里,我们称x是a关于b的乘法逆元

ax%b = 1,可以化成 ax = by + 1

显然,它等价于这样的表达式:ax + by = 1             

这个式子很眼熟有木有!!!如果等式右边是gcd(a,b)就好了!!!

然后这里copy欧几里德的三个定理:

定理一:如果d = gcd(a,b),则必能找到正的或负的整数x和y使d = ax + by 
定理二:若gcd(a,b) = 1,则方程ax≡c(mod b)在[0,b-1]上有唯一解
定理三:若gcd(a,b) = d,则方程ax≡c(mod b)在[0,b|d01]上有唯一解

于是,对上述方程 ax + by = 1, 当gcd(a,b) != 1的时候无解

故方程ax + by = c 有解的条件是 : c%gcd(a,b) = 0;

按乘法逆元讲,一般,我们能找到无数组解满足条件,但一般题目是求解最小的那组解

假设我们求出了特解x0,那么 ,只需要用x0 % b就是最小解了

为什么?(这个我没管,反正知道是这个。。。。后面贴的参考资料链接里面有。。。)

另外,有的时候求得的特解可能是个负数,或者说b是负数,怎么办?

如果b是负数,取b的绝对值

如果x0是负数,让x0对|b|取模后再加上|b|

然后,直接上模板代码:

int Cal(int a,int b)//求最小的x使ax+ by = 1
{
    int x,y;
    int gcd = e_gcd(a,b,x,y);
    if (1%gcd) return -1;//无解
    x*=1/gcd;
    b = abs(b);
    int ans = x%b;
    if (ans <= 0) ans += b;
    return ans;
}


下面给出求最小逆元的代码:

typedef long long ll;
ll e_gcd (ll a, ll b, ll& x, ll& y)
{
    if (b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    ll ans = e_gcd (b, a % b, y, x);
    y -= a / b * x; //这个和前面用的方法不一样,不过是对的,写起来更快、
    return ans;
}
ll Cal(ll a,ll b,ll c)//求最小的x使ax+ by = c
{
    ll x,y;
    ll gcd = e_gcd(a,b,x,y);
    if (c%gcd) return -1;//无解
    x*=c/gcd;
    b /= gcd;
    if (b < 0) b = -b;
    ll ans = x%b;
    if (ans <= 0) ans += b;
    return ans;
}

来一道入门级的裸的扩展欧几里德的题感受下模板怎么用:

PS:方程还是要自己列,然后用扩展欧几里德求解

青蛙的约会

AC代码供参考:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
ll e_gcd(ll a,ll b,ll &x,ll &y)
{
    if (b == 0)
    {
        x = 1,y = 0;
        return a;
    }
    ll ans = e_gcd(b,a%b,x,y);
    ll tmp = x;
    x = y;
    y = tmp - a/b*y;
    return ans;
}
ll cal(ll a,ll b,ll c)//求最小的x使ax+by=c
{
    ll x,y;
    ll gcd = e_gcd(a,b,x,y);
    if (c%gcd != 0) return -1;
    x *= c/gcd;
    b/=gcd;
    if (b < 0) b = -b;
    ll ans = x%b;
    if (ans <= 0) ans += b;
    return ans;
}
int main()
{
    ll xa,xb,va,vb,L;
    while (~scanf("%lld %lld %lld %lld %lld",&xa,&xb,&va,&vb,&L))
    {
        ll ans = cal(vb-va,L,xa-xb);
        if (ans == -1) puts("Impossible");
        else printf("%lld\n",ans);
    }
    return 0;
}
</
  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值