标签:
最近做了一些拓展欧几里得的题目呢,嘛,从一开始的不会到现在有点感觉,总之把我的经验拿出来和大家分享一下吧。
普通的欧几里得是用于解决求两个数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