数论 - 学习历程杂碎(1)

我是菜鸡
我是一个能把map用RE的菜鸡
模拟只会猜题意 贪心刚好过样例
搜索不行剪枝难 递归时常爆个栈
数学不好先打表 string只能KMP
DP只会套模板 组合数学看运气
图论上来死八法 计算几何瞎暴力
博弈打成DFS 数论只会GCD


只会gcd怎么行
天天被那些数论大火题爆踩Orz
菜鸡要崛起


GCD

额我们还是从简单的开始

gcd,最大公因数,
指两个或多个整数共有约数中最大的一个。

求它的方法有很多。

暴力

O(n)枚举即可

因数分解

把这些数分别分解质因数,然后在各个共有的素因数里,取出指数最小的乘方相乘即得最大公因数,O(???)

以上是2种naïve的求法。

学了数论,可以知道曾经有位大牛欧几里德给我们研究出了一种很cool的算法:

辗转相除法

(=欧几里德算法)

用较小数除较大数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。O(log n)

伪代码如下

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

但好像递归的常数很大来着
我们还可以把它再优化一下
让它20% cooler
具体来说就是应用xor的特性
可以把它mo改成这个形式

int gcd(int a,int b)
{
    while(b)b^=a^=b^=a%=b;
    return a;
}

就是酱紫


欧拉函数

一般叫做 fai phi。
这么写

φ

欧拉函数是小于n的正整数中与n互质的数的数目。(但是φ(1)=1)

欧拉函数的公式是
\[ \varphi (x) = x \times \prod^n_{i=1} (\frac{p_i -1}{p_i}) \]

其中\(p1,p2,... ,pn\)\(x\)的所有质因数。* 每一个质因数只能算一次!

代码可以写成这个样子

int fai(int x)
{
    int tmp=x;register int i;
    for(i=2;i*i<=x;i++)if(x%i==0){tmp=tmp*(i-1)/i;while(x%i==0)x/=i;}
    if(x>1)tmp=tmp*(x-1)/x;return tmp;
}

扩展欧几里德算法

还记得之前那个欧几里德算法么
可以用来算gcd
现在把它拓展一下
就可以求解像这样的方程式
\[ ax + by = gcd(a,b) \]
中的\(x\)\(y\).

问题来了
怎么球

欧几里德算法停止的状态是:a= gcd,b = 0,
这时候只要 a = gcd 的系数是 1 ,
就可以得到 \(a*1 + b*0 = gcd\)

当然这是最终状态,但是我们是否可以从最终状态反推到最初的状态呢?

假设当前我们要处理的是求出 a 和 b的最大公约数,
并求出x和y使得\(a*x + b*y= gcd\)
而我们已经求出了下一个状态:b 和 a%b 的最大公约数,
并且求出了一组x1 和y1 使得:\((a\mod b)*y1+b*x1= gcd\)
那我们怎么找出它们的关系呢?

我们知道: $ a \mod b=a-(int)(a/b)*b $
那么,我们可以进一步得到:
\(gcd = b*x1 + (a-(a/b)*b)*y1\)
\(gcd = b*x1 + a*y1 – (a/b)*b*y1\)
\(gcd = a*y1 + b*(x1 – a/b*y1)\)

我们求出来了x和y使得\(a*x + b*y= gcd\)
然后......
$ x \leftarrow y1$
$ y \leftarrow x1 – a/b*y1$

可以这么写

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

(d就是gcd(a,b))


乘法逆元

https://www.luogu.org/problem/show?pid=1082 .

求关于 x 的同余方程\(ax ≡ 1 (\mod b)\)的最小正整数解。

这不就是一道裸的求逆元题么
等会儿,逆元是什么?

对于正整数a和b,如果有\(ax ≡ 1 (\mod b)\)
那么把这个同余方程中的x最小正整数解叫做a mo b的逆元.

逆元可以用上面的那个扩展欧几里德算法得出.

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

这是刚才那段代码.

你问我逆元有什么用?
做题时如果结果过大一般都会让你mo一个数,而这个数一般是个非常巨大的质数,加减乘与mo运算的顺序交换不会影响结果,但是除法不行.
而有些丧心病狂的题目就是要mo一个巨大的质数,如果原本的结果中有除法,比如除以一个数n,直接可以让它精度原地起爆.
为了维护世界的和平防止意外出现,我们就可以把它换成乘n的逆元(倒数),效果都一样,只是更安全.

不过用扩欧求逆元有个条件
就是
\[ gcd(a,b)\mod b=1 \]
...

球逆元的话可以在上面那基础上再加一些代码

int inv(int a,int p)
{
    int d,x,y;
    gcdex(a,p,d,x,y);
    return d==1?(x+p)%p:-1;//gcdex有时候会出负数,所以要加一下再mo
    //还有,d是gcd(a,b)
}

顺便贴个模板题Luogu 1082的题解

#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gotcha()
{
    register int _a=0;bool _b=1;register char _c=getchar();
    while(_c<'0' || _c>'9'){if(_c=='-')_b=0;_c=getchar();}
    while(_c>='0' && _c<='9')_a=_a*10+_c-48,_c=getchar();
    return _b?_a:-_a;
}
void gcdex(int a,int b,int &d,int &x,int &y)
{
    if(!b)d=a,x=1,y=0;
    else gcdex(b,a%b,d,y,x),y-=x*(a/b);
}
int inv(int a,int p){int d,x,y;gcdex(a,p,d,x,y);return d==1?(x+p)%p:-1;}
int main()
{
    register int a=gotcha(),p=gotcha(),ans=inv(a,p);
    if(ans>=0)printf("%d",ans);//else puts("Fuck you");
    return 0;
}

施工中
但最近不想写(2)了

转载于:https://www.cnblogs.com/finder-iot/p/7657175.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值