最近做了一些关于扩展GCD的题,有点懵,现在有点头绪。就大致写写。
扩展GCD,即是算满足ax+by=gcd(a,b)该式的x,y的值。
而在一般解题过程中,往往需要变通。如:
在解ax+by=c时,需要先算出gcd(a,b)。然后判断c%gcd(a,b)==0是否成立。解的个数就是gcd(a,b)的个数。
个人理解如下:
ax+by=gcd(a,b)有解,而c%gcd(a,b)==0。所以c能整除gcd(a,b)。设c/gcd(a,b)=n。则ax+by=c可以化为ax+by=gcd(a,b)*n。再化为a*(x/n)+b*(y/n)=gcd(a,b)。所以恒有解。
然后是问题的具体解法:
1.判断c%gcd(a,b)
2.ax0+by0=gcd(a,b)已经根据扩展gcd公式算出。而需解a*(x/n)+b*(y/n)=gcd(a,b)。明显有x/n=x0,y/n=y0。所以解出x=x0*n y=y0*n ,即x=x0*(c/gcd(a,b)) 同理y=y=*(c/gcd(a,b))。
求解完成!
3.而题目一般会要求最小解。下面讨论如何求最小解:
我们设x通解为x0+t*n(x0为上面求得的x,t为正整数,c表示x解的间距)。同理y通解为y0+t*m。
所以有a*(x0+t*n)+b*(y0+t*m)=c恒成立。而a*x0+b*y0=c也是恒成立的。所以有a*t*n-b*t*m=0。因为我们需要通解t=0时x=x0,y=y0,所以当t!=0时,有a*n=b*m(a,b为已常数),所以要使n,m最小(因为n,m是x,y解的间距,需要最小),而又有解,需让a*n等于a,b的最小公倍数,b*m等于a,b的最小公倍数。
例:3*n=5*m 最小n,m分别是5,3。
而a,b最小公倍数为a*b/gcd(a,b)。所以解得n=b/gcd(a,b) m=a/gcd(a,b)
最小解x=(x0%n + n)%n y=(y0%m+m)%m
x0%n使x落于区间(-n~n), +n使的解为正数,再次%n使得解最小。最小y同理。
4.解答完毕!
下面是扩展GCD的模版:
int ExGcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
int t = ExGcd(b, a%b,x,y);
int temp = x;
x = y;//x1=y2
y = temp - (a / b)*y;
return t;
}
下面是对该式的说明:
1.显然当 b=0,gcd(a,b)=a。此时 x=1,y=0;
2.以下式子基于递归性质:
要求ax1+by1=gcd(a,b)的解:
由题意等量代换有:bx2+(a%b)y2=gcd(b,a%b)
而a%b = a- (a/b)*b
所以bx2+(a-(a/b)*b)*y2=gcd(b,a%b)=gcd(a,b)
整理得:ay2+b(x2-(a/b)*y2) = gcd(b,a%b) = gcd(a,b)
而ax1+by1=gcd(a,b)。所以有x1=y2 , y1=x2-(a/b)*y2。
一个例题:
//题目内容:
//计算循环语句的执行频次 for (i = A; i != B; i += C) x += 1;
//其中A, B, C, i都是k位无符号整数。
//输入描述
//A B C k, 其中0<k<32
//
// 输出描述
// 输出执行频次数,如果是无穷,则输出“forever”
//
// 输入样例
// 3 7 2 16
//
// 输出样例
// 2
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b)
{
return b == 0 ? a : gcd(b, a%b);
}
LL ex_gcd(LL a, LL b, LL &x, LL &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
LL ans = ex_gcd(b, a%b, x, y);
LL temp = x;
x = y;
y = temp - a / b*y;
return ans;
}
int main()
{
LL A, B, C, k;
cin >> A >> B >> C >> k;
LL a, x, b, y, n;
a = C;b = pow(2, k);n = B - A;
//原式转化为A+t*C=B (mod 2^k) t为整数,为要求的数
//t*c+y*2^k=B-A x即t
//继续转化 a=c,x=t,b=2^k,y=y,n=B-A
//即ax+by=n;需求x
LL temp = gcd(a, b);
if (!A&& !B && !C)
{
cout << 0 << endl;
return 0;
}
if (n%temp != 0 || temp==0)
{
cout << "forever" << endl;
return 0;
}
ex_gcd(a, b, x, y);//求得x0,y0
x = (n / temp)*x;//x为所得,但不一定为最小所得值
LL T = b / temp;//T为区间
x = ( x % T + T ) % T;//取得最小所得值
cout << x << endl;
// system("pause");
return 0;
}