韩信点兵
问题:给定了n组除数m[i]和余数r[i],通过这n组(m[i],r[i])求解一个x,使得x mod m[i] = r[i]
解:
一开始就直接求解多个方程组的解,比较困难,所以我们从 n = 2 开始递推
已知:
x mod m[1] = r[1] x mod m[2] = r[2]
所以存在两个数, k[1], k[2]
x = k[1]*m[1] + r[1] x = k[2]*m[2] + r[2]
因为值相同,所以:
k[1]*m[1] + r[1] = k[2]*m[2] + r[2] => k[1]*m[1] - k[2]*m[2] = r[2] - r[1]
令 A = m[1], B = m[2], C = r[2] - r[1], x = k[1], y = k[2] 则上式变为 Ax + By = C
则是扩展欧几里德,可以看我之前的一篇博客http://www.cnblogs.com/ygdblogs/p/5476395.html
由于每两个方程可以合并成一个,连续操作,转化为一个方程,即可求解多个方程组的解
伪代码:
M = m[1], R = r[1] For i = 2 .. N d = gcd(M, m[i]) c = r[i] - R If (c mod d) Then // 无解的情况 Return -1 End If (k1, k2) = extend_gcd(M / d, m[i] / d) // 扩展欧几里德计算k1,k2 k1 = (c / d * k1) mod (m[i] / d) // 扩展解系 R = R + k1 * M // 计算x = m[1] * k[1] + r[1] M = M / d * m[i] // 求解lcm(M, m[i]) R %= M // 求解合并后的新R,同时让R最小 End For If (R < 0) Then R = R + M End If Return R
源码:
#include<stdio.h> #include<iostream> typedef long long ll; ll m[1010], r[1010], x, y; ll gcd(ll a,ll b){ if(a%b == 0) return b; return gcd(b, a%b); } void extend_gcd(ll a,ll b){ if(b==0){ x=1; y=0; } else{ extend_gcd(b,a%b); ll t=x; x=y; y=t-(a/b)*x; } } int main(){ int n; ll a, b, c, ans, r1; scanf("%d",&n); for(int i =0; i<n; i++) scanf("%lld%lld",&m[i],&r[i]); a = m[0], r1 = r[0]; for(int i=1; i<n; i++){ b = m[i]; c = r[i] - r1; ll d = gcd(a, b); if(c%d){ ans=-1; break; } else{ ll a1 = a/d; ll b1 = b/d; ll c1 = c/d; extend_gcd(a1,b1); ll x1 = (x*c1)%b1; if(x1< 0) x1 += b1; r1 = a*x1 + r1; a = a1*b; ans = r1; } } printf("%lld ",ans); return 0; }