#1303 : 数论六·模线性方程组
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Ho:今天我听到一个挺有意思的故事!
小Hi:什么故事啊?
小Ho:说秦末,刘邦的将军韩信带领1500名士兵经历了一场战斗,战死四百余人。韩信为了清点人数让士兵站成三人一排,多出来两人;站成五人一排,多出来四人;站成七人一排,多出来六人。韩信立刻就知道了剩余人数为1049人。
小Hi:韩信点兵嘛,这个故事很有名的。
小Ho:我觉得这里面一定有什么巧妙的计算方法!不然韩信不可能这么快计算出来。
小Hi:那我们不妨将这个故事的数学模型提取出来看看?
小Ho:好!
<小Ho稍微思考了一下>
小Ho:韩信是为了计算的是士兵的人数,那么我们设这个人数为x。三人成排,五人成排,七人成排,即x mod 3, x mod 5, x mod 7。也就是说我们可以列出一组方程:
x mod 3 = 2
x mod 5 = 4
x mod 7 = 6
韩信就是根据这个方程组,解出了x的值。
小Hi:嗯,就是这样!我们将这个方程组推广到一般形式:给定了n组除数m[i]和余数r[i],通过这n组(m[i],r[i])求解一个x,使得x mod m[i] = r[i]。
小Ho:我怎么感觉这个方程组有固定的解法?
小Hi:这个方程组被称为模线性方程组。它确实有固定的解决方法。不过在我告诉你解法之前,你不如先自己想想怎么求解如何?
小Ho:好啊,让我先试试啊!
输入
第1行:1个正整数, N,2≤N≤1,000。
第2..N+1行:2个正整数, 第i+1行表示第i组m,r,2≤m≤20,000,000,0≤r<m。
计算过程中尽量使用64位整型。
输出
第1行:1个整数,表示满足要求的最小X,若无解输出-1。答案范围在64位整型内。
样例输入
3
3 2
5 3
7 2
样例输出
23
嘛,就是求解模线性方程组问题:
我们有一组方程 x mod m[i] = r[i] ,求解x。
嘛我们可以考虑两个方程组成的方程组:
X mod M =R (最初时是m[i]与r[i])
x mod m[i] = r[i] (1<=i<n)
=>
x = M * k1 + R ①
x = m[i] * k2+ r[i] ②
① — ② =>
M * k1 - m[i] *k2 = R - r[i] (形如 a*x + b*y =c)
先由Bézout定理判断是否有解,即c % gcd(a,b) == 0;
Bézout定理:
设gcd(a,b)=d,那么存在整数x,y,使得 ax + by = d。
换句话说,如果方程 ax + by = m 有解,那么 m 一定是 d 的若干倍。
所以当方程 ax + by = 1 有解时,gcd(a,b)= 1 !
那么求出 ax + by = m的一个解 (x,y)这样一个问题,可转化为求出 ax + by = gcd(a,b)的一个解 (x₀,y₀) ~
若存在解,则通过扩展欧几里德求出k1,k2;
扩展欧几里德:
先看不扩展得欧几里德,就是所谓得辗转相除法求最大公约数:
基于定理:两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。最大公约数(Greatest Common Divisor)缩写为GCD。
gcd(a,b) = gcd(b,a mod b) (不妨设a>b 且r=a mod b ,r不为0)。
那么如何进行扩展呢?扩展欧几里德就是在求 a,b 的最大公约数 d = gcd(a,b) 的同时,求出 ax + by = d的一个解 (x,y)。
我们可以从最后的状态看起:有 a * 1 + b * 0 = gcd
往前推上一个状态: b * x1 + (a%b) * y1 = gcd (a,b也在变化)
又: a%b = a - (a/b)*b(因为/是整除)
原式可转化为: b*x1 + (a-(a/b)*b)*y1 = b*x1 + a*y1 – (a/b)*b*y1 = a*y1 + b*(x1 – a/b*y1) = gcd
于是我们可以得到x,y的一层层转化过程: x = y1 , y = x1 – a/b*y1理解一下exgcd的实现代码:
LL exgcd(LL a,LL b,LL &x,LL &y) { if(b==0){ x=1;y=0; return a; } LL r=exgcd(b,a%b,x,y); LL tem=y; y=x-(a/b)*y;//y=x1-a/b*y1 x=tem;//x=y1; return r; }
另一种写法:
int exgcd(LL a,LL b,LL &x,LL &y) { if(b==0) { x=1;y=0; return a; } int r=exgcd(b,a%b,y,x); //交换位置,很精妙的写法啊 y-=(a/b)*x;//y=x1-a/b*y1 return r; }
扩展欧几里德算法可用于RSA加密等领域。(诶?
注意此时求出的k1并不是原方程的k1,还需要 *c/d 。
代入原方程求出x0,得到转化得新方程:
x = lcm(M,m[i]) * k +x0;
继续求解直至最后。
方程组求解新方程转化具体代码实现详见完整代码部分及其详细注释。
完整代码如下:
#include <cstdio>
#include <cstring>
#define LL long long
const int maxn=1020;
int n;
LL m[maxn],r[maxn];
LL k1,k2;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(b==0){
x=1;y=0;
return a;
}
LL r=exgcd(b,a%b,x,y);
LL tem=y;
y=x-(a/b)*y;//y=x1-a/b*y1
x=tem;//x=y1;
return r;
}
//b * x1 + (a%b) * y1 = gcd
//a%b = a - (a/b)*b(因为/是整除)
// b*x1 + (a-(a/b)*b)*y1
//= b*x1 + a*y1 – (a/b)*b*y1
//= a*y1 + b*(x1 – a/b*y1) = gcd
//x = y1 , y = x1 – a/b*y1
int main()
{
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%lld%lld",&m[i],&r[i]);
}
LL M=m[0],R=r[0];
for(int i=1;i<n;++i){
LL d=exgcd(M,m[i],k1,k2);
LL c=r[i]-R;
if(c%d){
printf("-1\n");
return 0;
}
//把k[1]代入x = m[1] * k[1] + r[1],就可以求解出x。
//同时我们将这个x作为///特解,可以扩展出一个解系:
//X = x + k*lcm(m[1], m[2]) k为///整数
//将其改变形式为:
//X mod lcm(m[1], m[2]) = x。
k1=c/d*k1%(m[i]/d); //c是d的几倍求出=c时的k
R=R+k1*M; // 计算x = m[1] * k[1] + r[1]
M=m[i]/d*M; //先除再乘防止溢出
R%=M; // 求解合并后的新R,同时让R最小
}
while(R<0) R+=M;
printf("%lld\n",R);
return 0;
}