数论—最大公约数
辗转相除法(欧几里得算法)
原理:GCD(x , y)= GCD(x , y-x)
核心:不断将两数规模变小,最后实现对数时间内把问题变换到能直接判定解的规模
代码实现:
int GCD(int x,int y)
{
return y==0?x:GCD(y,x%y);
}
二进制算法
通过不断去除因子2降低常数实现提高GCD的效率,避免了欧几里得算法中对余数的计算过程。
若x=y,则GCD(x,y)=x,否则:
(1)若x,y均为偶数,则GCD(x,y)=2*GCD(x/2,y/2);
(2)若x为偶数,y为奇数,则GCD(x,y)=2*GCD(x/2,y);
(3)若x为奇数,y为偶数,则GCD(x,y)=2*GCD(x,y/2);
(3)若x,y均为奇数,则GCD(x,y)=GCD(x-y,y);
代码实现:
inline int GCD(int x,int y)//欧几里得二进制算法优化
{
int i,j;
if(x==0)return y;
if(y==0)return x;
for(i=0;0==(x&1);i++)x>>=1;//去掉所有的2
for(j=0;0==(y&1);j++)y>>=1;//去掉所有的2
if(j<i)i=j;
while(1)
{
if(x<y)x^=y,y^=x,x^=y;//若x<y交换x,y
if(0==(x-=y))return y<<i;
//若x==y,gcd==x==y(就是在辗转减,while(1)控制)
while(0==(x&1))x>>=1;//去掉所有的2
}
}
x>>=1 右移运算符,将x的二进制表示向右移动1位,最高位补0,将此结果再赋值给x。
<<= 同理
[/demo]
#include<iostream>
using namespace std;
typedef long long ll;
inline ll abs(ll x){
return x<0?-x:x;
}
inline ll min(ll a,ll b){
return a<b?a:b;
}
inline ll gcd(ll a,ll b){
if(a==0)return b;
if(b==0)return a;
if(!(a&1)&&!(b&1))return 2*gcd(a>>1,b>>1);
else if(!(a&1))return gcd(a>>1,b);
else if(!(b&1))return gcd(a,b>>1);
else return gcd(abs(a-b),min(a,b));
}
int main(){
ll a,b;
cin>>a>>b;
cout<<"gcd="<<gcd(a,b);
return 0;
}
[/demo]
扩展欧几里得算法
用途:已知(a,b),求解一组(p,q),使得p*a+q*b=GCD(a,b)
首先呢,可以看出:a = gcd(a,b), b = 0 是该式的一个特解;
然后,根据欧几里得算法的原理可以得出:bx + (a%b)y = gcd(a,b);
又因为, a%b = a - (a/b)*b (a/b 为整除关系)
所以原式化为: bx + (a - (a/b)*b)y = gcd(a,b);
整理得: ay + b * (x - (a/b) * y) = gcd(a,b);
代码实现:
int extend_gcd(int a, int b, int &x, int &y)
{
if(b==0)
{
x = 1;
y = 0;
return a;
}
int r = extend_gcd(b, a%b, y, x);
y -= a/b*x; //这里已经是递归,回溯的过程了,x,y已经颠倒了
return r;
}
[/demo]
#include<bits/stdc++.h>
int extended_gcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
int r=extended_gcd(b,a%b,y,x)
y-=a/b*x;
return r;
}
int main()
{
int a,b,x,y;
scanf(“%d%d”,&a,&b);
z=extended_gcd(a,b,x,y);
printf(“%d%d%d\n”,z,x,y);
return 0;
}
[/demo]
求解线性同余方程
定理1:对于方程a*x+b*y=c,有整数解的充分必要条件是:c%GCD(a,b)=0。
定理2:若GCD(a,b)=1,且x0,y0为a*x+b*y=c的一组解,则该方程的任一解可表示为:x=x0+b*t,y=y0-a*t,且对任一整t,皆成立。
代码实现:
int extended_euclid(int a,int b,int&x,int&y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int d=extended_educlid(b,a%b,x,y);
int temp=x;
x=y;
y=temp-a/b*y;
return d;
}
//用扩展欧几里得算法解线性方程ax+by=c;
bool linearEquation(int a,int b,int&x,int&y)
{
int d=extended_euclid(a,b,x,y);
if(c%d)return false;
int k=c/d;
x*=k; //+t*b;
y*=k; //-t*a;
//求得只是其中一个解
return true;
}
训练心得:
一天下来,感觉没有收获很多东西。上午花了一个小时左右的时间把整本书浏览了一遍(个人习惯,看书前习惯上把翻一翻整本书,知道大体内容),然后下手看数论部分的内容。数论部分一共11小节,做了简单的计划,因为周二下午打比赛,所以计划周一和周三每天看4小节,周二看3小节。但一天下来,只看了前三节,前三节内容相对后面几节来说还是简单些,但是看完后也是似是而非,恍恍惚。进度没赶上,深度也差远了,似乎刚学这本书的这道坎很难迈过去(终于明白老师说的这次训练强度很大,要做好心理准备的意思了)。反思一下今天,最耗时的就是在最大公约数这一节中的二进制算法、扩展欧几里得算法、求解线性同余方程这几部分,每一部分都查看了几篇博客(讲真,只看书上的内容看半天还是一脸懵,这也是一本数论书压缩到几十页的弊端)参考着博客看知识点还是有用处的,但是熟练程度离老师要求还差远了。
所以,方法只有一个......
加班
还有,坐上一天腰是真的酸。