【校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;
}

校OJ上有个特殊点0 0 0 0需要输出forever

#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;
	scanf("%lld%lld%lld%lld",&A,&B,&C,&k);	
		if(A == 0 && B == 0 && C == 0 && k == 0){
			cout<<"forever"<<endl;
			return 0;
		}
		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;
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值