题目描述:
给定2n个整数a1,a2,…,an和m1,m2,…,mn,求一个最小的非负整数x,满足∀i∈[1,n],x≡mi(mod ai)。
输入格式
第1行包含整数n。第2..n行:每i+1行包含两个整数ai和mi,数之间用空格隔开。
输出格式
输出最小非负整数x,如果x不存在,则输出-1。如果存在x,则数据保证x一定在64位整数范围内。
数据范围
1≤ai≤2^31−1,0≤mi<ai,1≤n≤25
输入样例:
2
8 7
11 9
输出样例:
31
我们怎么思考这样的题呢?我们都会解多元一次方程,不断进行方程的合并即可。
那我们能用类似的思路来做这个同余方程嘛。答案是可以的。
设第一个式子是,那么我们可以得到一个式子。
同理我们得到。我们尝试合并一下这个式子得到
令得
是不是感觉很熟悉,合并两个同余方程,居然可以用拓展欧几里得算法来解。如果你不太了解拓展欧几里得算法,建议去查阅资料或者直接阅读我之前的文章拓展欧几里得算法-数论代码笔记(超级细节)
由拓展欧几里得算法我们能够算出,甚至我们还能知道如果不能整除 时,这个方程组无解。
那现在我们关心的是,这两个方程合并之后的方程能咋表示呢?
显然,我们需要把这个方程表示成类似于的形式。我们已经得出了,其中由拓展欧几里得算法求出,,为整数集。我们把这个东西代入,可以得到
因为我们想求最小的非负整数,所以最后一定是,从而
因为这个一定是正数,所以我们要求最小的m1,就一定要让之前的能取最小。
所以下面代码里面有两句话
k1 *= (m2 - m1) / d;
k1 = (k1 % (a2 / d) + a2 / d) % (a2 / d);
这样就能求出最小的,进而求出最小的,让这个作为我们新方程的
但是不能只改m,还需要改a,a怎么改呢,我们观察到最后x那一块里面有,也就是。我们直接令
ll a = abs(a1 / d * a2);
即可。
代码如下:
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long LL;
int n;
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, y, x);
y -= a / b * x;
return d;
}
LL inline mod(LL a, LL b){
return ((a % b) + b) % b;
}
int main(){
scanf("%d", &n);
LL a1, m1;
scanf("%lld%lld", &a1, &m1);
for(int i = 1; i < n; i++){
LL a2, m2, k1, k2;
scanf("%lld%lld", &a2, &m2);
LL d = exgcd(a1, -a2, k1, k2);
if((m2 - m1) % d){ puts("-1"); return 0; }
k1 = mod(k1 * (m2 - m1) / d, abs(a2 / d));
m1 = k1 * a1 + m1;
a1 = abs(a1 / d * a2);
}
printf("%lld\n", m1);
return 0;
}