1.中国剩余定理的推导 ≡
学习引用:
同余方程:x≡a(mod b) <=> x(mod b)=a(mod b) <=> x%b=a%b;
乘法逆元:如果有a/b≡a*x(mod p)
我们可以等价变换为:
a/b=a*b^(-1)(表示逆元)=a/b*b*b^(-1)(mod p)
化简可得:b^b^(-1)≡1(modp)
则由费马小定理可得到 b^(-1)=b^(p-2);
由于 扩展欧几里得算法求逆元可以得到:
exgcd(a,b,x,y)中x表示a在a(mod b)下的乘法逆元
数学知识:x/3 余 1
2*x/3 余 2
问题背景进行推论:
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”。
问题告诉我们了:
假设一个数是x,那么这个x满足:1. x%3=2; 2. x%5=3; 3.x%7=2;
我们可以写成:x=3*k+2;x=5*k+ 3;x=7*k+2;
让我们求得最小的x是多少;(-^<>^-)
问题求解:
x=3*k+2; x=5*k+3; x=7*k+2;
从这三个公式进行入手我们可以把问题分解为:
现在 1.x是5和7的倍数并且除以3余下2
2.x是3和7的倍数并且除以5余下3
3.x是3和5的倍数并且除以7余下2
这三个问题的和是和总问题等价的;
那么我们让M=3*5*7;
当解决第一个问题时我们让M1= M/3;
那么M1就是5和7的倍数了,之后我们求他除以3余下2的
我们现在求得数假设为 Mi*k
那么有Mi*k(mod 3)=2 <=> Mi*k ≡ 2(mod 3);
Q:不会求;A:我也不会……
我们知道的是b^b^(-1) ≡1(mod p)
那么 Mi^Mi^(-1) ≡1(mod p);
那么我们求出Mi ^(-1) *2不就是k吗
问题一的答案不就是 Mi^(-1) *2 *Mi 吗
随着问题一的解决后面的解法就迎刃而解了,真不错
那么对于更一般的呢我们就要发现:
对于多组的同余问题:
x ≡ a1(mod m1)
x ≡ a2(mod m2)
x ≡ a3(mod m3)
……………
令M=m1*m2*m3……;
Mi=M/mi;
ans=Mi*Mi^(-1)*ai;
求和;最后是最小所以还要取余他们的乘积
最后上代码:
题目:P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <iostream> #include <cstdio> #include <fstream> #include <algorithm> #include <cmath> #include <deque> #include <vector> #include <queue> #include <string> #include <cstring> #include <map> #include <stack> #include <set> #include <bitset> using namespace std; #define int long long const int N = 1e5 + 10; const int INF = 0x3f3f3f3f; const long long LLINF = 4e18 + 5; typedef long long LL; typedef pair<int, int> PII; #define xx first #define yy second #define endl '\n' struct node { // bool operator<(const node &a) const // { // } }; void ClearFloat() { ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); } int read() { int ret = 0, f = 1; char ch = getchar(); while ('0' > ch || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ('0' <= ch && ch <= '9') { ret = ret * 10 + ch - '0'; ch = getchar(); } return ret * f; } int n,a[N],m[N],Mi[N],M=1,ans=0; int exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1, y = 0; return a; } int d = exgcd(b, a % b, x, y); int z = x; x = y; y = z - y * (a / b); return d; } signed main() { ClearFloat(); cin>>n; for(int i=1;i<=n;i++) { cin>>m[i]; M*=m[i]; cin>>a[i]; } for(int i=1;i<=n;i++) { Mi[i]=M/m[i]; int x=0,y=0; exgcd(Mi[i],m[i],x,y); ans+=a[i]*Mi[i]*(x%m[i]+m[i]); } cout<<ans%M; }
2.扩展中国剩余定理
中国剩余定理的局限性:
模数必须是满足两两互质,对于这种情况我们要想办法得到一个更普遍的算法。那么扩展中国剩余定理便得以应用。
首先我们回到最开始的问题:
问题背景进行推论:
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?。
学习引用:
1.乘法逆元的欧几里得算法。
2.欧几里得算法里的:&x与&y 表示的是 ax+by=gcd(a,b)的一组解;
那么如果求ax+by=c的一组解的话 其有解当且仅当 gcd(a,b)|c,c能把gcd(a,b)整除掉
那么就有ax+by=k*gcd(a,b),那么我们知道x0,y0是ax+by=gcd(a,b)的一组解时那么他的特解就是k*x0,ky0;k=c/gcd(a,b);那么通解
x=k*x0+w*b/gcd(a,b),y=c/d-w*a/gcd(a,b);w是一个未知数
问题求解:
我们已经知道了
x=a1*k1+b1; x=a2*k2+b2; x=a3*k3+b3;
我们两两进行观察:
x=a1*k1+b1=a2*k2+b2;
对a1*k1+b1=a2*k2+b2进行整理可以得到:
a1*k1-a2*k2=b2-b1;
这个公式和上面的ax+by=c是形式相近的;
我们求exgcd(a1,-a2,&x,&y)可以求出a1*k1-a2*k2=gcd(a1,-a2)的 一组解
我们求得通解为:
1.若(b2-b1)%gcd(a1,-a2) ! = 0 =>无解
2.k1=k11*(b2-b1)/(gcd(a1,-a2));
k2=k22*(b2-b1)/(gcd(a1,-a2));
3. 我们知道:
k1=k11+k*a2/gcd(a1,-a2)
k2=k22+k*a1/gcd(a1,-a2)
不妨让k11=k1%abs(a2/gcd(a1,-a2))
k22=k2%abs(a1/(gcd(a1,-a2));
x=a1*(k11+k*(b2-b1)/(gcd(a1,-a2)))+b1
=k11*a1+m+k*lcm(a1,a2)
让a1=lcm(a1,a2),b=k11*a1+b1
(其中通过d=exgcd(a,b,x,y),lcm(a1,a2)=a1*a2/d;k11=x*(b2-b1)/d%abs(a2/d))
再次变为x=k*a1+b的形式不断轮回
题目:AcWing 204. 表达整数的奇怪方式 - AcWing
代码:
#include <iostream> #include <cstdio> #include <fstream> #include <algorithm> #include <cmath> #include <deque> #include <vector> #include <queue> #include <string> #include <cstring> #include <map> #include <stack> #include <set> #include <bitset> using namespace std; // #define int long long const int N = 1e5 + 10; const int INF = 0x3f3f3f3f; const long long LLINF = 4e18 + 5; typedef long long LL; typedef pair<int, int> PII; #define xx first #define yy second #define endl '\n' struct node { // bool operator<(const node &a) const // { // } }; void ClearFloat() { ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); } int read() { int ret = 0, f = 1; char ch = getchar(); while ('0' > ch || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ('0' <= ch && ch <= '9') { ret = ret * 10 + ch - '0'; ch = getchar(); } return ret * f; } int n; int a[N], m[N]; LL exgcd(LL a, LL b, LL &x, LL &y) { if (b == 0) { x = 1, y = 0; return a; } int d = exgcd(b, a % b, x, y); int z = x; x = y; y = z - y * (a / b); return d; } int main() { ClearFloat(); cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i] >> m[i]; } LL m1 = m[1], a1 = a[1]; for (int i = 2; i <= n; i++) { LL k1, k2; LL d = exgcd(a1, -a[i], k1, k2); if ((m[i] - m1) % d) { cout << "-1"; return 0; } k1 = k1 * (m[i] - m1) / d; k1 = (k1 % abs(a[i] / d) + abs(a[i] / d)) % (abs(a[i] / d)); m1 = k1 * a1 + m1; a1 = abs(a1 / d * a[i]); } cout << m1 << endl; }
仔细揣摩一下才会懂得