「笔记」大数翻倍法求解中国剩余定理
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
「笔记」⼤数翻倍法求解中国剩余定理⽬录
注:做法和思路是 zhx 在⼀次讲课中提出的,如有侵权,请联系作者删除
其实别的题解也有提到过暴⼒做法,但这⾥将会给出更加严谨的复杂度的证明
想看普遍的那个推导⽅法的话可以来
Update :证明部分⽤费马⼩定理是错误的。
修正了证明过程。
正⽂
引⼊
我们知道,中国剩余定理是⼀种⽤来求解类似于
x ≡a 1(mod m 1)x ≡a 2(mod m 2)x ≡a 3(mod m 3)...x ≡a 4(mod m 4)
形式的同余⽅程组的定理,要求我们找出 x 的最⼩⾮负整数解⼤数翻倍法
现在市⾯上⽐较推⼴的⼀种⽅法是⽤扩展欧⼏⾥得来求解同余⽅程组。
这⾥将介绍⼀种更为暴⼒的算法——⼤数翻倍法,写起来也更加⽅便简洁。
先来考虑两个同余⽅程的情况:
x ≡a 1(mod m 1)x ≡a 2(mod m 2)考虑⽤⼀种暴⼒的⽅法将其合并成⼀个同余⽅程。
让我们设初始的 x =0,m =1,合并了第⼀个⽅程后变为 x =a 1,m =m 1。
那么现在只需要满⾜第⼆个同余⽅程即可。
我们知道 (a 1+km 1)mod m 1=a 1,⼀个显然的想法是每次暴⼒的加 m 1,然后暴⼒的判断能否满⾜第⼆个同余⽅程。
找到⼀个能满⾜的情况合并即可,模数合并为 lcm(m 1,m 2),代码也⼗分好写,只有四⾏:
void Merge(LL &a1, LL &m1, LL a2, LL m2) {
while(a1 % m2 != a2) a1 += m1;
m1 = Lcm(m1, m2);
}复杂度证明
设满⾜情况时加了 k 次 m 1,那么有:
a 1+km 1≡a 2
(mod m 2)
假设 k =m 2,即加了 m 2 次,那么:
a 1+m 2m 1≡a 1
(mod m 2)所以说加 k 次所得到的模数的循环节的⼤⼩为 m 2。
所以上⾯代码每次合并的复杂度是 O (m 2) 的。
如果⼀次枚举超过了 m 2 次还没有得到答案,那么我们可以判定这组同余⽅程⽆解,因为在循环下去也还是那些余数,没有什么意义了。
(就是加了个枚举的限制⽽已
发现更⼩的模数的复杂度更优,所以我们添⼀句优化,通过特判转换⼀下枚举的模数即可。
代码改为:
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);
}
所以总的复杂度为 O (∑n i =1m i )。
但是!它的复杂度真的有那么⾼吗?(那我也没必要写这篇博客了是吧
我们知道答案⼀定在 long long 范围内,并且 ∏n i =1m i ⼀定也不会爆 long long 。
因为⾼精度求解同余⽅程组也没那个做法是吧,出题⼈也⼀定不会出个爆 long long 的样例,因为他⾃⼰也做不了。
让我们来考虑最坏情况:{
{
想要卡我们,每个模数都得是⼀个⼤质数。
还要保证成绩和在 long long 范围内(也就是 1018)。
那么只有⼀种情况,n=2!此时m i可以做到 2×109级别的⼤质数。
总时间复杂度为O(109) ,可以被卡。
但是,当n=3 时,m i只有 106级别,我们的复杂度也只有O(3×106) ,可以通过。
n更⼤的情况就不必说了吧。
⼀个出题⼈不可能把所有数据都搞成n=2 且m i在 109级别。
⼤数翻倍法的优势
码量⼩
理解难度⼩
⼀般不会被卡,没有⼈会对着这个⾮主流算法卡⼗个点的
不需要考虑模数互质的情况
最后的最后:上代码!
/*
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
Processing math: 100%。