标签:
最近做了一些拓展欧几里得的题目呢,嘛,从一开始的不会到现在有点感觉,总之把我的经验拿出来和大家分享一下吧。
普通的欧几里得是用于解决求两个数a,b的gcd的,但是我们知道,gcd是线性组合 { ax+by | x,y∈Z }里的最小正元素(什么?不知道怎么来的?好吧。。。算法导论里数论算法那一章有证明),假若我们能够把这个x和y找出来,那么可以用来解决很多问题。
(以下的gcd和lcm均指(gcd(a,b)和lcm(a,b))
首先,假设ax+by=gcd这一个方程有一个特解x*,y*。那么显然,我们可以构造出这个方程的通解x0=x*+k*lcm/a,y0=y*-k*lcm/b,k∈Z。拓展欧几里得就能够帮我们把x*和y*求出来,这是用途之一。
其次,假若我们面对的方程是ax+by=d的形式,解是整数的情况下,我们可以利用上面说的解来推导出这个方程的解。
①假若gcd 不能整除 d,那么此时这个方程无整数解
证明: 假设存在整数解 n ,m 使得 an+bm=d,那么此时左右两边同时除以gcd有
a/gcd*n+b/gcd*m=d/gcd 显然,左边的和依然是整数,右边是分数,因为gcd不能整除d ,矛盾
所以此方程无整数解,证毕。
②在gcd是d的一个因子的情况下,这个方程可以变成 a*(x*gcd/d) + b*(y*gcd/d) = gcd,于是就转化成为了第一个问题的求解。但此时在求解通解的时候需要注意,该方程的通解并不是原来的通解乘上一个d/gcd这么简单。我想有一些人会这么做
x*gcd/d=x0,y*gcd/d=y0,因此,此时的通解就是 x = d*x0/gcd, y= y0*d/gcd
假若仅仅只是原来的通解乘上一个d/gcd,那么这只是现在方程的通解的一个子集而已,并不是通解。举个例子,假设d/gcd=2,那么你原来的k∈Z,现在乘上一个d/gcd之后,k的范围就变成了偶数,丢失了一部分的解。正确的是仅仅只使用原来的特解,也就是下面这一种情况。
x = d*(x*)/gcd + k*lcm/a , y = d*(y*)/gcd + k*lcm/b
只有这样的情况才是这个方程的通解,我做的很多题目都是需要对k的范围加以限定,然后选出合适的解
再者,可以用来求解一次同余式的问题,相信这个转化不是很难 。假设我们遇见的方程是这样的 ax≡ d(mod b),我们可以轻易的变成 ax + by = d的形式,也就是上面讨论过的那一个形式,不过在这里我们只需要一个x即可。
接下来讲解例题
POJ 1061 青蛙的约会
这一道题目就是上面说的第三种情况,求解一次同余式的问题,这道题目的一个式子就是 x + k*n ≡ y + k *m (mod L )通过移项后可以变成 k*(n-m)≡ y-x(mod L )这就是完完全全的第三种情况了,不过要保证k是正数,我们需要对k的范围作出限定,我们在求出特解之后在特解上进行加减的操作,保证k是恰好大于等于0的那一个解即可。这里的pair分别代表两只青蛙,pair的第一维是起始坐标,第二维是跳跃长度
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;
pair<long long,long long>A,B,ans;
long long L,a,b,g,d,x,y;
long long gcd(long long a,long long b){return a%b==0?b:gcd(b,a%b);}
bool flag;
void egcd(long long a,long long b,long long& d,long long& x,long long& y);
int main(){
while(scanf("%lld%lld%lld%lld%lld",&A.first,&B.first,&A.second,&B.second,&L)!=EOF){
if(A.second<B.second)
swap(A,B); //保证n-m是正数
long long k=B.first-A.first;
a=A.second-B.second;
long long times;
if(k<0){
times=-k/L;
k+=times*(L+1);
}
if(k%gcd(a,L)!=0){ //从这里开始就是求解第二种情况了
printf("Impossible\n");
continue;
}
egcd(a,L,d,x,y);
x*=k/d;
if(x<0){
times=-x/(L/d);
x+=(times+1)*(L/d);
}
else if(x>0){
times=x/(L/d);
x-=times*(L/d);
}
printf("%lld\n",x);
}
return 0;
}
void egcd(long long a,long long b,long long& d,long long& x,long long& y){
(b==0)?(d=a,x=1,y=0):(egcd(b,a%b,d,y,x),y-=x*(a/b));
}
POJ 2142 C Looooops 和上面的青蛙的题差不多,也是一个求解同余式的问题,在这里就是判断 A+ x*C ≡ B (mod (1<<k))这个方程是不是有解,做法和上面一样,也需要保证x是正数,在此不赘述。
#include<cstdio>
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b){return a%b==0?b:gcd(b,a%b);};
ll a,b,c,d,k,x,y,times,mod;
void egcd(ll a,ll b,ll& d,ll& x,ll& y){(b==0)?(d=a,x=1,y=0):(egcd(b,a%b,d,y,x),y-=x*(a/b));};
int main(){
while(scanf("%lld%lld%lld%lld",&a,&b,&c,&d)&&d){
mod=1LL<<d;
if(b-a<0)
times=(a-b)/mod,k=(b-a)+(times+1)*mod;
else
k=b-a;
if(k%gcd(c,mod)!=0){
printf("FOREVER\n");
continue;
}
egcd(c,mod,d,x,y);
x*=k/d;
if(x<0)
times=-x/(mod/d),x+=(times+1)*(mod/d);
else if(x>0)
times=x/(mod/d),x-=times*(mod/d);
printf("%lld\n",x);
}
return 0;
}
UVA 10090 Marbles 这道题目就是显然的第二种情况了,不过我们需要保证求出来的两个解都是正数的情况下的最小值。按照我们上面的通解公式,求解两个不等式x>0和y>0,得到k的范围,然后写出一个有关代价Cost的算术函数,然后分析在k的定义域内取得极值的条件就可以了。其实把代价函数写出来之后发现是一个有关于k的一次函数,但是斜率是和两个物品的性价比有关系,因此只需要额外拿出来讨论一下即可,总之是在两个端点取得极值(函数自己写吧,这里就不给出了)
#include<cstdio>
#include<utility>
#include<cmath>
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);};
pair<ll,ll> pa,pb;
ll n,x,y,k,r,lcm,times,down,up;
ll egcd(ll a,ll b,ll& x,ll& y){return b==0?(x=1,y=0):(egcd(b,a%b,y,x),y-=x*(a/b));};
int main(){
while(scanf("%lld",&n)&&n){
scanf("%lld%lld%lld%lld",&pa.first,&pa.second,&pb.first,&pb.second);
k=gcd(pa.second,pb.second);
if(n%k!=0){
printf("failed\n");
continue;
}
egcd(pa.second,pb.second,x,y);
r=n/k;lcm=pa.second*pb.second/k;
x*=r,y*=r;
down=ceil((pa.second*-x*1.0)/lcm),up=floor((y*1.0*pb.second)/lcm);
if(down>up){
printf("failed\n");
continue;
}
if(pa.first*pb.second<=pb.first*pa.second)
x+=up*lcm/pa.second,y-=up*lcm/pb.second;
else
x+=down*lcm/pa.second,y-=down*lcm/pb.second;
printf("%lld %lld\n",x,y);
}
return 0;
}
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
ll a,b,d,x,y,k,lcm,ans;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
void egcd(ll a,ll b,ll& x,ll& y){b==0?(x=1,y=0):(egcd(b,a%b,y,x),y-=x*(a/b));}
pair<ll,ll> pa,pb,pc,pn;
void judge();
bool flag=false;
int main(){
while(scanf("%lld%lld%lld",&a,&b,&d)&&d){
k=gcd(a,b),lcm=a*b/k;
egcd(a,b,x,y);
ans=ceil(max(y*1.0*b*d/lcm/k,-a*1.0*x*d/lcm/k));
pa.first=d/k*x+ans*lcm/a,pa.second=-d/k*y+ans*lcm/b;
ans=ceil(max(-b*y*1.0*d/k/lcm,a*x*1.0*d/k/lcm));
pb.first=-d/k*x+ans*lcm/a,pb.second=d/k*y+ans*lcm/b;
if(d>=a+b&&ceil(-a*x*d*1.0/k/lcm)<=floor(b*y*d*1.0/k/lcm)){
flag=true;
if(b-a<0)
ans=floor(b*y*1.0*d/lcm/k);
else if(b-a>0)
ans=ceil(-a*x*d*1.0/lcm/k);
pc.first=x*d/k+ans*lcm/a,pc.second=y*d/k-ans*lcm/b;
}
judge();
printf("%lld %lld\n",pn.first,pn.second);
flag=false;
}
return 0;
}
void judge(){
if(pa.first+pa.second<pb.second+pb.first)
pn=pa;
else if(pa.first+pa.second>pb.second+pb.first)
pn=pb;
else
pn=(a*pa.first+b*pa.second<a*pb.first+b*pb.second)?pa:pb;
if(flag){
if(pc.first+pc.second<pn.first+pn.second)
pn=pc;
else if(pc.first+pc.second==pn.first+pn.second&&a*pc.first+b*pc.second<pn.first*a+b*pn.second)
pn=pc;
}
}
UVA 10673 Play with Floor and Ceil 这道题真是太简单了。。。。既然任意解都可以的话。。通解是很明显的。。。因为floor(x)+1 == ceil(x) 在x不是整数情况下,答案很明显。。。
#include<cstdio>
using namespace std;
typedef long long ll;
ll t,a,b;
int main(){
scanf("%lld",&t);
while(t--){
scanf("%lld%lld",&a,&b);
if(a%b!=0)
printf("%lld %lld\n",-a,a);
else
printf("0 %lld\n",b);
}
return 0;
}
以上,拓展欧几里得例题讲解到此完毕,假若有函数不会写的也可以在下面留言。。其实我感觉只要把题目分析好了,就像个数学题。。重点是在草稿本上的推导。。
拓展欧几里得详解 及其题目 POJ 1061 2115 2142 UVA 10673 10090
标签:
原文地址:http://blog.csdn.net/good_night_sion_/article/details/52274156