代码改变世界

POJ 2891 Strange Way to Express Integers 非互质的中国剩余定理

2012-07-31 09:09  javaspring  阅读(182)  评论(0编辑  收藏  举报

来源:http://poj.org/problem?id=2891

题意:有一个数x,x = r[i] (mod a[i]),其中a[i] 和 a[j] 不一定互质,求x的最小值。

思路:很容易看到这题和中国剩余定理是有联系的。因为倘若任意的a[i] 和 a[j] 互质,则满足中国剩余定理。中国剩余定理还是好理解的,这里就不多说了,主要说一下不互质情况下如何转化为互质下的中国剩余定理。

转化主要是用了合并的思想。设 x = r1 (mod a1)  x = r2 (moda2),则易知 x = k1 * a1 + r1,x = k2 * a2 + r2,因此k1 * a1  + r1 = k2 * a2 + r2,化简后可得,k1 * a1= (r2 – r1) + k2 * a2,因为k2 * a2 % a2 = 0,所以k1 * a1 = (r2 – r1) (mod a2),因此我们很容易判断有解无解的条件,若gcd(a1,a2)| (r2 – r1),则有解,否则无解。设d = gcd(a1,a2),c = (r2 – r1),则a1*k1/d = (r2 –r1)/d (mod a2/d),通过化简后,我们可得k1 = c/d * (a1/d)^(-1) (mod a2 / d),其中(a1/d)^(-1)代表(a1/d)对于(a2/d)的乘法逆元。这点解释一下,因为是对(a2/d)取余,所以是对(a2/d)的乘法逆元很好理解,在说为什么是乘法逆元。x乘法逆元的概念就是y,使得x*y % n = 1,由于a1/d 和a1/d 的倒数的乘积为1,故他们的乘积对n取余为1,满足乘法逆元。因此(a1/d)^(-1)是关于(a2 / d)的乘法逆元。由于c/d * (a1/d)^(-1)可以算出来,即已知。所以我们令K = c/d * (a1/d)^(-1),即k1 = K ( mod a2/d),所以k1 = y * a2/d + K,将此式代入 x = k1 * a1 + r1,得x = (a1 * K + r1) (mod a1 * a2 / d),我们令r = (a1 * K +r1),a = a1 * a2 / d,因此我们得到一个新的式子,即 x = r (mod a),注意这个式子和开始我们所写的式子格式是一样的。也就是说,两个式子能够合并成一个,然后继续合并,易知最后肯定会合并为一个式子。我们设最后所合并的式子为x = r (mod a),求x的最小值,即x = (r%a + a) % a就是最终答案。至此,这道题已经解决。

代码:

#include <iostream>
#include <cstdio>
#include <string.h>
using namespace std;

#define CLR(arr,val) memset(arr,val,sizeof(arr))
const int N = 1010;
typedef long long ll;
ll gcd(ll a,ll b){
	if(b == 0)
		return a;
	return gcd(b,a%b);
}
void extend_Eulid(ll a,ll n,ll &x,ll &y){
	if(n == 0){
	  x = 1;
	  y = 0;
	  return;
	}
	extend_Eulid(n,a%n,x,y);
	ll temp = x;
	x = y;
	y = temp - a/n * y;
}
ll inv(ll a,ll n){
	ll d = gcd(a,n);
	if(d != 1)
		return -1;
	ll x,y;
	extend_Eulid(a,n,x,y);
	return (x % n + n) % n;
}
bool merge(ll a1,ll r1,ll a2,ll r2,ll &a3,ll &r3){
	ll d = gcd(a1,a2);
	ll c = r2 - r1;
	if(c % d)
		return false;
	c = (c % a2 + a2) % a2;
	a1 /= d;
	a2 /= d;
	c /= d;
	c *= inv(a1,a2);
	c %= a2;
	c *= (a1*d);
	c += r1;
	a3 = a1 * a2 * d;
	r3 = (c % a3 + a3) % a3;
	return true;
}
ll China_Remain(ll n,ll a[N],ll r[N]){
	ll a1 = a[1],r1 = r[1];
	for(ll i = 2;i <= n;++i){
		ll a2,r2,a3,r3;
	  a2 = a[i];
	  r2 = r[i];
	 // printf("a1 = %lld  r1 = %lld\n",a1,r1);
	  if(!merge(a1,r1,a2,r2,a3,r3)){
		  //printf("i == %d\n",i);
		  return -1;
	  }
	  a1 = a3;
	  r1 = r3;
	}
	return (r1 % a1 + a1) % a1;
}
int main(){
	//freopen("1.txt","r",stdin);
	ll n,a[N],r[N];
	while(scanf("%lld",&n) != EOF){
	  CLR(a,0);
	  CLR(r,0);
	  for(ll i = 1;i <= n;++i)
		  scanf("%lld%lld",&a[i],&r[i]);
	  ll ans = China_Remain(n,a,r);
	  printf("%lld\n",ans);
	}
	return 0;
}