1. 程式人生 > >【校OJ】擴充套件gcd-時間複雜性 (10分)

【校OJ】擴充套件gcd-時間複雜性 (10分)

GCD

gcd就是最大公約數的意思

我們首先要了解擴充套件gcd,首先要知道gcd,也就是歐幾里得演算法

即gcd(a,b) = gcd(b, a mod b)   a,b的最大公因數 = b,a mod b的最大公因數

證明如下:

a = kb + r 則 r = a mod b

假設d 為 a b 的最大公約數(d = gcd(a,b))

那麼d 也是 r 的最大公約數(因為 r = a - kbd既可以被a除斷,也可以被b除斷,所以也能被r除斷)

即d 是 a mod b的最大公約數(d = gcd(b,a mod b))

從另一頭開始證明

假設d 為 b a mod b的最大公約數(d = gcd(b,a mod b))

a = kb + r,d也是a的最大公約數

所以d = gcd(a, b)

得證

並且當b == 0時,a就是a,b的最大公約數(這個我也不造怎麼證明,就記下來叭)

有模板如下

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

擴充套件GCD

擴充套件gcd不僅可以求出ab的最大公約數,還可以求出ax + by = gcd(a,b)方程的解

有結論如下x1 = y2, y1 = x2 - (a / b * b)y2  

為啥呢?

證明如下:

我們由歐幾里得演算法可得gcd(a,b) = gcd(b,a % b),那麼有下面三個等式

ax1 + by1 = gcd(a,b)……(1)

bx2 + (a % b)y2 = gcd(b,a%b)……(2)

(1)= (2)

=> ax1 + by1 = bx2 + (a - (a / b) * b)y2 = ay2 + b(x2 - (a / b)* y2)

所以:

x1 = y2

y1 = x2 - (a / b * b)y2 證畢

並且b == 0時 x = 1 y = 0(just remember this)//d=a,x=1,y=0,此時等式d=ax+by成立

有模板如下

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b == 0){
		x = 1; y = 0;
		return a;
	}
	ll d = exgcd(b, a%b, x, y);
	ll xt = x;
	x = y;
	y = xt - a / b * y;
	return d;
}

 推廣

如果我們要求ax + by = n的解呢?

也可以用擴充套件gcd,求出gcd(a,b)之後,如果n不能被gcd(a,b)整除,就是無解的

如果可以被整除,我們可以得到一組特解x0,y0

由特解可以得到通解的通項公式(為什麼成立?直接帶到ax + by = gcd(a,b)裡試試)

x = x0 + b/gcd(a,b) * t

y = y0 - a / gcd(a,b) * t(t是一個整數)

通常題目需要我們求出方程組最小解

在這裡b/gcd(a,b),a/gcd(a,b)分別是x,y的最小區間,也就是說

x在0 ~ b/gcd(a,b)上只有一個整數解

y在0 ~ a/gcd(a,b)上只有一個整數解

那麼這個解就是我們要求的最小整數解

為什麼最小區間是b / gcd(a,b)?

證明如下:

設有對特解為x0,y0,x的最小區間為c,y的最小區間為d,則有

x = x0 + ct

y = y0 - dt

帶入ax + by = n

ax + by

= a (x0 + ct) + b (y0 - dt)

= ax0 + by0 + act - bdt = n

因為ax0 + by0 = n

所以act - bdt = 0

所以ac = bd,想要用最小的c和d讓式子成立,我們就得讓ac = bd = ab / gcd(a,b)(其實這個地方思路有點跳,我也不太懂)

可得c = b / gcd(a,b), d = a / gcd(a,b),證畢

所以,最後求得的x = (x0 % (b / gcd(a,b)) + b / gcd(a,b))%(b / gcd(a,b))

x0%(b / gcd(a,b))讓x的範圍落在- b / gcd(a,b) ~ + b / gcd(a,b)上 再加上一個 b / gcd(a,b),區間落在 0 ~ 2(b / gcd(a,b))上

再取模b/gcd(a,b)區間就落在0 ~ b / gcd(a,b)就是我們要求的最小值

下面來看一道題目:POJ2115

644.擴充套件gcd-時間複雜性 (10分)

C時間限制:3000 毫秒 |  C記憶體限制:3000 Kb

題目內容:

計算迴圈語句的執行頻次 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 

題目要求(a + xc ) % 2 ^ k 等不等於b(t是任意正整數)

若有解有表示式(a + xc)mod 2^k == b

那麼a + xc - b = y * 2^k(y是任意正整數)

cx - 2^k * y = b - a

是不是很像

ax + by = n

我們就把這些數字帶進去用exgcd做就可以啦(~ ̄▽ ̄)~

程式碼如下:

//POJ2115
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y){
	if(b == 0){
		x = 1; y = 0;
		return a;
	}
	LL d = exgcd(b, a%b, x, y);
	LL xt = x;
	x = y;
	y = xt - a / b * y;
	return d;
}
int main(){
	LL A,B,C,k;
	while(scanf("%lld%lld%lld%lld",&A,&B,&C,&k)){	
		if(A == 0 && B == 0 && C == 0 && k == 0){
			break;
		}
		LL a = C;
		LL b = (LL)1 << k;
		LL n = B - A;
		LL x,y;
		LL d = exgcd(a,b,x,y);

		if(n % d){
			cout<<"FOREVER"<<endl;
		}else{
			//x = (x0 % (b / gcd(a,b)) + b / gcd(a,b)) % b / gcd(a,b) 
			x = (x * (n / d))%b;

			x = (x % (b / d) + (b / d)) % (b / d);
			cout<<x<<endl;
		}
	}
		
	
	return 0;
}