注:做法和思路是 zhx 在一次讲课中提出的,如有侵权,请联系作者删除
其实别的题解也有提到过暴力做法,但这里将会给出更加严谨的复杂度的证明
正文
引入
我们知道,中国剩余定理是一种用来求解类似于
\[\begin{cases}x \equiv a_1 \pmod {m_1} \\x \equiv a_2 \pmod {m_2} \\x \equiv a_3 \pmod {m_3} \\... \\x \equiv a_4 \pmod {m_4} \\\end{cases}\]
形式的同余方程组的定理,要求我们找出
我们知道答案一定在 long long
范围内,并且 \(\prod_{i=1}^{n} m_i\) 一定也不会爆 long long
。
因为高精度求解同余方程组也没那个做法是吧,出题人也一定不会出个爆 long long
的样例,因为他自己也做不了。
让我们来考虑最坏情况:
想要卡我们,每个模数都得是一个大质数。还要保证成绩和在 long long
范围内(也就是 \(10^{18}\))。
那么只有一种情况, \(n = 2\)!此时 \(m_i\) 可以做到 \(2 \times 10^9\) 级别的大质数。总时间复杂度为 \(O(10^9)\) ,可以被卡。
但是,当 \(n = 3\) 时, \(m_i\) 只有 \(10^6\) 级别,我们的复杂度也只有 \(O(3 \times 10^6)\) ,可以通过。
\(n\) 更大的情况就不必说了吧。
大数翻倍法的优势
- 码量小
- 理解难度小
- 一般不会被卡,没有人会对着这个非主流算法卡十个点的
- 不需要考虑模数互质的情况
最后的最后:上代码!
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 大数翻倍法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
LL n, v, d, a, b;
LL read(){
LL s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
LL Gcd(LL x, LL y) { return !y ? x : Gcd(y, x % y); }
LL Lcm(LL x, LL y) { return x / Gcd(x, y) * y; }
void Merge(LL &a1, LL &m1, LL a2, LL m2) {
if(m1 < m2) swap(m1, m2), swap(a1, a2);
while(a1 % m2 != a2) a1 += m1;
m1 = Lcm(m1, m2);
}
int main()
{
n = read(); v = 0, d = 1; // 初始化
for(int i = 1; i <= n; ++i) a = read(), b = read(), b %= a, Merge(v, d, b, a);
printf("%lld", v);
return 0;
}
如果觉得写的不错就点个赞吧这个做法顶上去吧/kel