1.最大公因数( g c d gcd gcd):
解法:辗转相除法:
原理: g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)
证明:
假设
a
>
b
a>b
a>b ,那么
a
a
a 可以表示成
a
=
k
∗
b
+
r
a = k*b + r
a=k∗b+r(
a
,
b
,
k
,
r
a,b,k,r
a,b,k,r 皆为正整数,且
r
<
b
r<b
r<b),
则
r
=
a
%
b
r=a\%b
r=a%b。
假设
d
d
d 是
a
,
b
a,b
a,b 的一个公约数,记作
d
∣
a
,
d
∣
b
d|a,d|b
d∣a,d∣b。
而
r
=
a
−
k
∗
b
r = a - k*b
r=a−k∗b,两边同时除以
d
d
d,得:
r
d
=
a
d
−
k
∗
b
d
=
m
\frac{r}{d}=\frac{a}{d}-\frac{k*b}{d}=m
dr=da−dk∗b=m;
由等式右边可知
m
m
m 为整数,因此
d
∣
r
d|r
d∣r;
因此
d
d
d 也是
b
,
a
%
b
b\;,\;a\%b
b,a%b 的公约数;
假设
d
d
d 是
b
,
a
%
b
b,a\% b
b,a%b 的公约数, 则
d
∣
b
,
d
∣
(
a
−
k
∗
b
)
d|b,d|(a-k*b)
d∣b,d∣(a−k∗b) 是一个整数,进而
d
∣
a
d|a
d∣a 。
因此
d
d
d 也是
a
,
b
a,b
a,b 的公约数;
因此
(
a
,
b
)
(a,b)
(a,b) 和
(
b
,
a
%
b
)
(b,a\%b)
(b,a%b) 的公约数是一样的,其最大公约数也必然相等,
得证。
代码:
int _gcd(int x,int y)
{
return y?_gcd(y,x%y):x;
}
也可以直接利用 algorithm \text{algorithm} algorithm 库中的函数 __gcd(x,y) \text{\_\_gcd(x,y)} __gcd(x,y)。
2.最小公倍数( l c m lcm lcm):
两个数 a , b a,b a,b 的最小公倍数:
ans=a/gcd(a,b)*b;//注意先除后乘,否则容易溢出
3.唯一分解定理:
定义:
任意一个大于
0
0
0 的正整数都能被表示成若干个素数的乘积且表示方法是唯一的。
即
n
=
p
1
a
1
∗
p
2
a
2
∗
.
.
.
∗
p
k
a
k
n=p_1^{a_1}*p_2^{a_2}*...*p_k^{a_k}
n=p1a1∗p2a2∗...∗pkak
应用:
(1)
g
c
d
gcd
gcd 为
a
,
b
a,b
a,b 两数所有相同质因子最小指数幂的乘积。
(2)
l
c
m
lcm
lcm 为
a
,
b
a,b
a,b 两数所有的质因子的最大指数幂的乘积。
可以拓展到多个数。
(3)一个数因子个数:
n
=
(
1
+
a
1
)
∗
(
1
+
a
2
)
∗
(
1
+
a
3
)
∗
.
.
.
∗
(
1
+
a
k
)
n=(1+a_1)*(1+a_2)*(1+a_3)*...*(1+a_k)
n=(1+a1)∗(1+a2)∗(1+a3)∗...∗(1+ak)
(4)一个数的所有因子和:
n
=
∏
i
=
1
k
∑
j
=
0
a
i
p
i
j
=
(
1
+
p
1
1
+
p
1
2
+
.
.
.
+
p
1
a
1
)
∗
(
1
+
p
2
1
+
p
2
2
+
.
.
.
+
p
2
a
2
)
∗
.
.
.
(
1
+
p
k
1
+
p
k
2
+
.
.
.
+
p
k
a
k
)
n=\prod_{i=1}^{k}{\sum_{j=0}^{a_i}{p_i^{j}}}=(1+p_1^1+p_1^2+...+p_1^{a_1})*(1+p_2^1+p_2^2+...+p_2^{a_2})*...(1+p_k^1+p_k^2+...+p_k^{a_k})
n=i=1∏kj=0∑aipij=(1+p11+p12+...+p1a1)∗(1+p21+p22+...+p2a2)∗...(1+pk1+pk2+...+pkak)
质因子分解代码:
复杂度: O ( n 1 2 ) O(n^{\frac{1}{2}}) O(n21)
//注意初始化
cnt=0;
for(int i=2;i*i<=num;i++)//sqrt(n)的复杂度
{
if(num%i==0)
{
factor[++cnt]=i;//存质因子
while(tn%i==0)
{
e[cnt]++;//质因子的幂
num/=i;
}
}
}
if(num>1)//不能省略!
{
factor[++cnt]=num;
e[cnt]++;
}
还可以预处理出素数表来加快分解速度。
大整数质因子分解:Pollard Rho因数分解
4.素数筛法:
埃氏筛:
实现:
首先将
2
2
2 到
n
n
n 范围内的整数写下来,其中
2
2
2 是最小的素数。将所有的
2
2
2 的倍数筛去,剩下的最小的数字就是
3
3
3 ,它不能被更小的数整除,所以
3
3
3 是素数。再将所有的
3
3
3 的倍数划去……以此类推。如果剩余的最小的数是
m
m
m,那么
m
m
m 就是素数。然后将所有
m
m
m 的倍数划去,像这样反复操作,就能依次枚举
n
n
n 以内的素数。
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
代码:
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e5+5;
vector<int>prime;//存素数
bool vis[N];//vis=false为素数
void Sieve()
{
memset(vis,0,sizeof(vis));
vis[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i])
{
prime.pb(i);
for(int j=i;j<N;j+=i)//建议用加法
//for(int j=1;j*i<N;j++)
vis[j]=1;
}
}
}
int main()
{
Sieve();
for(int i=0;i<10;i++)
cout<<prime[i]<<endl;
return 0;
}
不足:一个数可能会被不同的质因子筛多次,造成时间的浪费。
欧拉筛(线性筛):
保证了每个合数只会被它的最小素因子筛掉,就把复杂度降到了 O ( N ) O(N) O(N)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
bool vis[N];//标记非素数
vector<int>prime;//保存素数
void euler()
{
prime.clear();
memset(vis,0,sizeof(vis));
for(int i=2;i<N;i++)
{
if(!vis[i])
prime.push_back(i);
for(int j=0;j<prime.size()&&i*prime[j]<N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)//重点
break;
}
}
}
int main()
{
euler();
return 0;
}
应用:
求积性函数。
5.快速幂:
定义:
要求
a
b
a^b
ab,朴素做法是把
a
a
a 连乘
b
b
b 次。时间复杂度为
O
(
n
)
O(n)
O(n)。
把
b
b
b 拆成二进制,该二进制数第
i
i
i 位的权为
2
i
2^i
2i。
所以
a
b
=
a
2
k
+
.
.
.
+
2
0
a^b=a^{2^k+...+2^0}
ab=a2k+...+20
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll power(ll a,ll b,ll mod)
{
ll res=1;
while(b)
{
if(b&1)
res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res%mod;
}
int main()
{
cout<<power(1LL*100,1LL*100,1LL*11)<<endl;
return 0;
}
6.逆元(乘法逆元):
定义:
若
a
∗
x
=
1
(
m
o
d
p
)
a*x=1 (mod \;p)
a∗x=1(modp),则
x
x
x 称为
a
a
a 在模
p
p
p 意义下的逆元,即
x
=
i
n
v
(
a
)
x=inv(a)
x=inv(a)。
(
a
+
b
)
%
p
=
(
a
%
p
+
b
%
p
)
%
p
(a + b) \% p = (a\%p + b\%p) \%p
(a+b)%p=(a%p+b%p)%p (√)
(
a
−
b
)
%
p
=
(
a
%
p
−
b
%
p
)
%
p
(a - b) \% p = (a\%p - b\%p) \%p
(a−b)%p=(a%p−b%p)%p (√)
(
a
∗
b
)
%
p
=
(
a
%
p
∗
b
%
p
)
%
p
(a * b) \% p = (a\%p * b\%p)\%p
(a∗b)%p=(a%p∗b%p)%p (√)
(
a
/
b
)
%
p
=
(
a
%
p
/
b
%
p
)
%
p
(a / b) \% p = (a\%p / b\%p) \%p
(a/b)%p=(a%p/b%p)%p (
×
\times
×)
求法:
(1)费马小定理:
当
p
p
p 为素数,
a
a
a 为正整数,且
a
,
p
a,p
a,p 互质时,有
a
(
p
−
1
)
=
1
(
m
o
d
p
)
a^{(p-1)}=1 (mod \;p)
a(p−1)=1(modp)
故
i
n
v
(
a
)
=
a
p
−
2
inv(a)=a^{p-2}
inv(a)=ap−2。
用快速幂取模求解即可。
复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
(2)拓展欧几里得:
a
∗
x
=
1
(
m
o
d
p
)
⟹
a
∗
x
+
p
∗
y
=
1
a*x=1 (mod\;p) \Longrightarrow a*x+p*y=1
a∗x=1(modp)⟹a∗x+p∗y=1
转化和求
x
x
x 的最小正整数解(转化为
[
0
,
p
)
[0,p)
[0,p) 内即可)。
要求满足
g
c
d
(
a
,
p
)
=
1
gcd(a,p)=1
gcd(a,p)=1,否则方程无解,逆元不存在。
复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
(3)递推求逆元:
求
[
1
,
p
)
[1,p)
[1,p) 内的逆元。
复杂度:
O
(
n
)
O(n)
O(n)
公式:
i
n
v
[
i
]
=
(
p
−
p
/
i
)
∗
i
n
v
[
p
%
i
]
%
p
inv[i]=(p-p/i)*inv[p\%i]\%p
inv[i]=(p−p/i)∗inv[p%i]%p
推导:
设
p
=
k
∗
i
+
r
p=k*i+r
p=k∗i+r,有
k
∗
i
+
r
=
0
(
m
o
d
p
)
k*i+r=0 (mod\;p)
k∗i+r=0(modp)
即
r
=
−
k
∗
i
(
m
o
d
p
)
r=-k*i(mod \;p)
r=−k∗i(modp)
两边同时除以
r
∗
i
r*i
r∗i,有
i
n
v
[
i
]
=
−
k
∗
i
n
v
[
r
]
(
m
o
d
p
)
inv[i]=-k*inv[r](mod\;p)
inv[i]=−k∗inv[r](modp)
将
k
,
r
k,r
k,r 替换掉,
i
n
v
[
i
]
=
(
−
k
)
∗
i
n
v
[
p
%
i
]
%
p
inv[i]=(-k)*inv[p\%i]\%p
inv[i]=(−k)∗inv[p%i]%p
因为
(
−
k
)
%
p
=
(
p
−
k
)
%
p
(-k)\%p=(p-k)\%p
(−k)%p=(p−k)%p,所以有
i
n
v
[
i
]
=
(
p
−
p
/
i
)
∗
i
n
v
[
p
%
i
]
%
p
inv[i]=(p-p/i)*inv[p\%i]\%p
inv[i]=(p−p/i)∗inv[p%i]%p
初始化
i
n
v
[
1
]
=
1
inv[1]=1
inv[1]=1,即可递推求出所有逆元。
补充:
[
1
,
p
)
[1,p)
[1,p) 内的所有数的逆元对于
[
1
,
p
)
[1,p)
[1,p) 内的所有数。
代码:
void get_inv()
{
inv[1]=1;
for(int i=2;i<p;i++)
inv[i]=(p-p/i)*inv[p%i]%p;
}
阶乘逆元:
求解步骤:
可以先求出最后一个数的阶乘(
n
!
n!
n!)的逆元,然后利用一下公式向前推导:
i
n
v
[
i
]
=
i
n
v
[
i
+
1
]
∗
(
i
+
1
)
inv[i]=inv[i+1]*(i+1)
inv[i]=inv[i+1]∗(i+1)
公式证明:
1 i ! = 1 ( i + 1 ) ! ∗ ( i + 1 ) \frac{1}{i!}=\frac{1}{(i+1)!}*(i+1) i!1=(i+1)!1∗(i+1)
7.素数分布定理:
定义:
对正整数
x
x
x,记
π
(
x
)
π(x)
π(x) 为不大于
x
x
x 的素数个数。
lim
x
→
∞
π
(
x
)
x
/
l
n
(
x
)
=
1
\lim\limits_{x\rightarrow\infty}\frac{ \pi(x)}{x/ln(x)}=1
x→∞limx/ln(x)π(x)=1
大致分布表:
x x x | π ( x ) \pi(x) π(x) |
---|---|
1 0 1 10^1 101 | 4 4 4 |
1 0 2 10^2 102 | 25 25 25 |
1 0 3 10^3 103 | 168 168 168 |
1 0 4 10^4 104 | 1 , 229 1,229 1,229 |
1 0 5 10^5 105 | 9 , 592 9,592 9,592 |
1 0 6 10^6 106 | 78 , 498 78,498 78,498 |
1 0 7 10^7 107 | 644 , 579 644,579 644,579 |
1 0 8 10^8 108 | 5 , 761 , 455 5,761,455 5,761,455 |
1 0 9 10^9 109 | 50 , 847 , 534 50,847,534 50,847,534 |
1 0 10 10^{10} 1010 | 455 , 052 , 511 455,052,511 455,052,511 |
例题:2019 ICPC Asia Xuzhou Regional C. < 3 <3 <3 numbers
8.组合数:
组合数公式:
C ( n , m ) = n ∗ ( n − 1 ) ∗ ( n − 2 ) ∗ . . ∗ ( n − m + 1 ) m ! = n ! ( n − m ) ! ∗ m ! (1) C(n,m)=\frac{n*(n-1)*(n-2)*..*(n-m+1)}{m!}=\frac{n!}{(n-m)!*m!} \tag{1} C(n,m)=m!n∗(n−1)∗(n−2)∗..∗(n−m+1)=(n−m)!∗m!n!(1)
C ( n , m ) = C ( n , n − m ) (2) C(n,m)=C(n,n-m)\tag{2} C(n,m)=C(n,n−m)(2)
线性递推:
C ( n , k ) = C ( n , k − 1 ) ∗ ( n − k + 1 ) k C(n,k)=C(n,k-1)*\frac{(n-k+1)}{k} C(n,k)=C(n,k−1)∗k(n−k+1)
杨辉三角:
递推式:
C
(
n
,
m
)
=
C
(
n
−
1
,
m
)
+
C
(
n
−
1
,
m
−
1
)
C(n,m)=C(n-1,m)+C(n-1,m-1)
C(n,m)=C(n−1,m)+C(n−1,m−1)
复杂度:
O
(
n
2
)
O(n^2)
O(n2)
代码:
void init()
{
for(int i=0;i<=n;i++)
{
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++)
C[i][j]=C[i-1][j-1]+C[i-1][j];
}
}
二项式定理:
公式:
( x + y ) n = C ( n , 0 ) ∗ x 0 ∗ y n + C ( n , 1 ) ∗ x 1 ∗ y ( n − 1 ) + . . . + C ( n , n ) ∗ x n ∗ y 0 (x+y)^n=C(n,0)*x^0*y^n+C(n,1)*x^1*y^{(n-1)}+...+C(n,n)*x^n*y^0 (x+y)n=C(n,0)∗x0∗yn+C(n,1)∗x1∗y(n−1)+...+C(n,n)∗xn∗y0
9.位运算:
(1)判断
n
n
n 是否为
2
2
2 的整数次幂:
n
&
(
n
−
1
)
=
=
0
n\&(n-1)==0
n&(n−1)==0;
(2)找到
n
n
n 中最低的
1
1
1 位:n&(~(n-1));
(3)
a
+
b
=
a
⨁
b
+
2
∗
(
a
&
b
)
a+b=a\bigoplus b+2*(a\&b)
a+b=a⨁b+2∗(a&b),异或是不进位的加法;
(4)或(
∣
|
∣) 可以用来判断子集;
10.同余方程:
一元线性同余方程:
欧几里得算法:
g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)
拓展欧几里得:
方程
a
x
+
b
y
=
g
c
d
(
a
,
b
)
ax+by=gcd(a,b)
ax+by=gcd(a,b),必然有解。
证明:
当
b
=
0
b=0
b=0 时,有
g
c
d
(
a
,
b
)
=
a
gcd(a,b)=a
gcd(a,b)=a,
{
x
0
=
1
y
0
=
0
\begin{cases}x_0=1\\y_0=0\end{cases}
{x0=1y0=0 为一组解。
当
b
!
=
0
b\;!=0
b!=0时,
a
∗
x
1
+
b
∗
y
1
=
g
c
d
(
a
,
b
)
=
b
∗
x
2
+
(
a
%
b
)
∗
y
2
a*x_1+b*y_1=gcd(a,b)=b*x_2+(a\%b)*y_2
a∗x1+b∗y1=gcd(a,b)=b∗x2+(a%b)∗y2
⇒
a
∗
x
1
+
b
∗
y
1
=
b
∗
x
2
+
(
a
−
(
a
/
b
)
∗
b
)
∗
y
2
\Rightarrow a*x_1+b*y_1=b*x_2+(a-(a/b)*b)*y_2
⇒a∗x1+b∗y1=b∗x2+(a−(a/b)∗b)∗y2
⇒
a
∗
x
1
+
b
∗
y
1
=
a
∗
y
2
+
b
∗
(
x
2
−
(
a
/
b
)
∗
y
2
)
\Rightarrow a*x_1+b*y_1=a*y_2+b*(x_2-(a/b)*y_2)
⇒a∗x1+b∗y1=a∗y2+b∗(x2−(a/b)∗y2)
所以: { x 1 = y 2 y 1 = x 2 − ( a / b ) ∗ y 2 \begin{cases} x_1=y_2\\y_1=x_2-(a/b)*y_2 \end{cases} {x1=y2y1=x2−(a/b)∗y2
据此,一直向下递归,直到到达边界 b = 0 b=0 b=0。然后回溯,利用不同方程的解的关系求解。
三种常见应用:
1.求解不定方程:
对于
a
x
+
b
y
=
c
ax+by=c
ax+by=c 的不定方程,
{
c
%
g
c
d
(
a
,
b
)
!
=
0
,
无
整
数
解
c
%
g
c
d
(
a
,
b
)
=
0
,
有
整
数
解
\begin{cases}c\%gcd(a,b)!=0,无整数解\\c\%gcd(a,b)=0,有整数解 \end{cases}
{c%gcd(a,b)!=0,无整数解c%gcd(a,b)=0,有整数解
当
c
%
g
c
d
(
a
,
b
)
=
0
c\%gcd(a,b)=0
c%gcd(a,b)=0 时,
先求解方程
a
∗
x
+
b
∗
y
=
g
c
d
(
a
,
b
)
a*x+b*y=gcd(a,b)
a∗x+b∗y=gcd(a,b),然后把解同时乘以
c
/
g
c
d
(
a
,
b
)
c/gcd(a,b)
c/gcd(a,b),即可得出原方程的解。
假设
x
0
,
y
0
x_0,y_0
x0,y0 为方程的一组解,
则通解:
{ x = x 0 + b / g c d ( a , b ) ∗ k y = y 0 − a / g c d ( a , b ) ∗ k \begin{cases}x=x_0+b/gcd(a,b)*k\\y=y_0-a/gcd(a,b)*k\end{cases} {x=x0+b/gcd(a,b)∗ky=y0−a/gcd(a,b)∗k
把通解代入原方程,可以发现恒成立。
x x x 的最小正整数解: x = ( x % b + b ) % b x=(x\%b+b)\%b x=(x%b+b)%b
注意:在一些问题中 a , b a,b a,b 等系数可能为负。
2.求解模线性方程(线性同余方程):
可以转化为求解不定方程。
3.求解模的逆元:
转化为求解不定方程。
拓展欧几里得解不定方程代码:
typedef long long ll;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
ll ans=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-(a/b)*y;
return ans;
}
ll cal(ll a,ll b,ll c)
{
ll x,y;
ll gcd=exgcd(a,b,x,y);
if(c%gcd)
return -1;
x=c/gcd*x;
x=(x%b+b)%b;
return x;
}
11.中国剩余定理:
一元线性同余方程组:
x
=
a
1
(
m
o
d
m
1
)
x=a_1(mod\ m_1)
x=a1(mod m1)
x
=
a
2
(
m
o
d
m
2
)
x=a_2(mod\ m_2)
x=a2(mod m2)
x
=
a
3
(
m
o
d
m
3
)
x=a_3(mod\ m_3)
x=a3(mod m3)
x
=
a
4
(
m
o
d
m
4
)
x=a_4(mod\ m_4)
x=a4(mod m4)
…
x
=
a
n
(
m
o
d
m
n
)
x=a_n(mod\ m_n)
x=an(mod mn)
其中
m
1
,
m
2
,
.
.
.
,
m
n
m_1,m_2,...,m_n
m1,m2,...,mn 两两互质,对于任意的
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an 方程组有解。
可以把解构造成如下形式:
x
=
p
1
+
p
2
+
p
3
+
.
.
.
+
p
n
x=p_1+p_2+p_3+...+p_n
x=p1+p2+p3+...+pn,
其中
p
1
%
m
1
=
a
1
,
.
.
.
,
p
n
%
m
n
=
a
n
p_1\%m_1=a_1\ ,...,\ p_n\%m_n=a_n
p1%m1=a1 ,..., pn%mn=an,且
p
i
%
∏
j
=
1
,
j
≠
i
n
m
j
=
0
p_i\%\prod_{j=1,j\neq i}^{n}{m_j}=0
pi%∏j=1,j=inmj=0,
那么这样的
x
x
x 一定满足条件。
以求
p
1
p_1
p1 为例:
假设
t
m
=
∏
i
=
1
,
i
≠
1
n
m
i
tm=\prod_{i=1,i\neq1}^{n}{m_i}
tm=∏i=1,i=1nmi,
考虑 t m ∗ t = 1 ( m o d m 1 ) tm*t=1(mod\ m_1) tm∗t=1(mod m1),
两边同时乘 a 1 a_1 a1,
有 t m ∗ t ∗ a 1 = a 1 ( m o d m 1 ) tm*t*a_1=a_1(mod\ m_1) tm∗t∗a1=a1(mod m1),那么 p 1 = t m ∗ t ∗ a 1 p_1=tm*t*a_1 p1=tm∗t∗a1 就满足条件了。
因此关键在于求 t t t,可以发现 t t t 为 t m tm tm 的逆元,直接用拓展欧几里得即可求解。
模板代码:(FZU - 1402)
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
ll m[12],aa[12];
int n;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
ll ans=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-(a/b)*y;
return ans;
}
ll crt()
{
ll M=1,ans=0;
for(int i=1;i<=n;i++)
M*=m[i];
for(int i=1;i<=n;i++)
{
ll tm=M/m[i];
ll x,y;
ll gcd=exgcd(tm,m[i],x,y);
ans=(ans+tm*aa[i]*x)%M;
}
ans=(ans%M+M)%M;//最小正整数解
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%I64d%I64d",&m[i],&aa[i]);
printf("%I64d\n",crt());
}
return 0;
}
拓展CRT:
对于一般情况,
m
1
,
m
2
,
m
3
,
.
.
.
,
m
n
m_1,m_2,m_3,...,m_n
m1,m2,m3,...,mn 不互质。
主要思路是:两两合并
分析:
首先,对于前两个方程:
{
x
=
a
1
+
m
1
∗
x
1
x
=
a
2
+
m
2
∗
x
2
\begin{cases}x=a_1+m_1*x_1\\ x=a_2+m_2*x_2\end{cases}
{x=a1+m1∗x1x=a2+m2∗x2
合并有:
a
1
+
m
1
∗
x
1
=
a
2
+
m
2
∗
x
2
a_1+m_1*x_1=a_2+m_2*x_2
a1+m1∗x1=a2+m2∗x2
⇒
m
1
∗
x
1
+
m
2
∗
x
2
=
(
a
2
−
a
1
)
\Rightarrow m_1*x_1+m_2*x_2=(a_2-a_1)
⇒m1∗x1+m2∗x2=(a2−a1)
利用拓展欧几里得即可解出该方程的最小的
x
1
x_1
x1 正整数解:
x
1
′
x_1^{'}
x1′
代入原第一个方程中,就可以求解出满足第一个方程的最小正整数解
x
′
=
x
1
′
∗
m
1
+
a
1
x^{'}=x_1^{'}*m_1+a_1
x′=x1′∗m1+a1
且该解同样满足方程
2
2
2,即
x
′
=
a
2
+
m
2
∗
x
2
x^{'}=a_2+m_2*x_2
x′=a2+m2∗x2 ,但
x
2
x_2
x2 不必求出。
那么满足所有方程的解
x
x
x 的形式,一定满足
x
=
x
′
+
k
∗
l
c
m
(
m
1
,
m
2
)
x=x^{'}+k*lcm(m_1,m_2)
x=x′+k∗lcm(m1,m2)。
至此,我们就可以把原来的两个方程合并为一个方程:
x
=
x
′
+
l
c
m
(
m
1
,
m
2
)
∗
k
x=x^{'}+lcm(m_1,m_2)*k
x=x′+lcm(m1,m2)∗k
即把方程的解当作余数,
l
c
m
lcm
lcm 当作模数,与其他方程形式相同。
以此类推,把所有方程合并,即可求出最后的解。
模板代码(luoguP4777):
#include <bits/stdc++.h>//最小非负整数x
using namespace std;
typedef long long ll;
const int N=1e5+5;
ll m[N],va[N];
ll mul(ll a,ll b,ll mod)
{
ll res=0;
while(b>0)
{
if(b&1) res=(res+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return res;
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll gcd=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-(a/b)*y;
return gcd;
}
ll cal(ll a,ll b,ll c)
{
ll x=0,y=0;
ll gcd=exgcd(a,b,x,y);
if(c%gcd)
return -1;
b/=gcd;
x=mul(x,c/gcd,b);//注意溢出
x=(x%b+b)%b;
return x;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&m[i],&va[i]);
ll r=va[1],t=m[1];//x=r+t*x',为合并后的方程
for(int i=2;i<=n;i++)
{
ll x=cal(t,m[i],(va[i]-r)%m[i]+m[i]);//保证常数c为正,使用快速乘
//x=x*t+r;
//r=x;
r=x*t+r;
t=m[i]/__gcd(m[i],t)*t;
r%=t;
}
printf("%lld\n",(r%t+t)%t);
return 0;
}