【基础总结】——数学知识
最大公约数与最小公倍数
理解起来挺简单的
重要等式:
l
c
m
[
a
,
b
]
×
g
c
d
(
a
,
b
)
=
a
×
b
lcm[a,b]\times gcd(a,b)=a\times b
lcm[a,b]×gcd(a,b)=a×b
所以只要知道最大公约数就行。
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
该代码运用了辗转相除法。因为 ( a , b ) = ( b , a 模 b ) (a,b)=(b,a模b) (a,b)=(b,a模b),所以一直模模模模模模模模直到 b b b为0,那么此时的最大公约数就是 a a a,然后递归回去。
算数唯一分解定理
定义:任一大于1的整数n 能表示成质数的乘积
,且其分解的结果是唯一的[不考虑次序].
直接上代码理解
void fenjie(int n)
{
js=0;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
js++,p[js]=i,c[js]=0;
while(n%i==0) n/=i,c[js]++;
}
}
if(n>1) js++,p[js]=n,c[js]=1;
}
p
p
p是底数,
c
c
c是指数
1.找n的因数,只要它能除下这个数,就一直除除除除除除除,直到除除不下,别忘了指数也要++。
2.这样一直循环,直到找完了n的所有因子,但是n可能是个质数,所以如果一直没有找到它的因子,那么他就只能分成
n
1
n^1
n1了
线性筛找素数( N N N)
用腻了之前简单的试除法,来看看这个线性筛吧
for(int i=2;i<=n;i++)
{
if(f[i]==0) f[i]=i,ans[++cnt]=i;
for(int j=1;j<=cnt;j++)
{
if(ans[j]>f[i]||ans[j]>n/i) break;
f[i*ans[j]]=ans[j];
}
}
f
[
i
]
f[i]
f[i]记录的是
i
i
i的最小质因子
,而
a
n
s
[
i
]
ans[i]
ans[i]记录的是目前找到的质数
1.线性筛其实是基于埃氏筛的,所以简单说一下。之前我们找素数是每个都判断,但是一个数的倍数一定不是质数,所以埃氏筛在找到一个质数的时候了,把这些质数的倍数都标记了。
2.但是聪明的某某发现,这样子会有大量重复标记,比如30会被2,3,5,标记,所以他想到用一个数的最小质因子来标记
,这样每个数就只被标记了一次。
3.可以来简单证明一下,如果不用最小质因子标记,那么30可能会被
3
×
10
3\times 10
3×10标记,但是10能分成
2
×
5
2\times 5
2×5,所以此时30一定被标记了两次。
4.对应代码倒数第二行,当质数表里的数大于了这个数的最小质因子,那么就
b
r
e
a
k
break
break,并且,如果这个数标记的数超过
n
n
n了,也不用管了.
扩展欧几里得
裴蜀定理:
a
x
+
b
y
=
c
ax+by=c
ax+by=c方程有解时当且仅当
c
c
c时
g
c
d
(
a
,
b
)
gcd(a,b)
gcd(a,b)的倍数(若
a
,
b
a,b
a,b互素,则
a
x
+
b
y
=
1
ax+by=1
ax+by=1有解)
欧几里得算法的核心是把
(
a
,
b
)
(a,b)
(a,b)辗转为
(
b
,
a
%
b
)
(b,a \%b)
(b,a%b),假设
(
b
,
a
%
b
)
(b,a\%b)
(b,a%b)存在对应的
(
x
1
,
y
1
)
(x1,y1)
(x1,y1)使得
b
×
x
1
+
(
a
%
b
)
×
y
1
=
c
b\times x1+(a\%b)\times y1=c
b×x1+(a%b)×y1=c
根据:
a
x
+
b
y
=
b
×
x
1
+
(
a
%
b
)
×
y
1
=
b
×
x
1
+
(
a
−
[
a
/
b
]
×
b
)
×
y
1
=
a
×
y
1
+
b
×
(
x
1
−
[
a
/
b
]
×
y
1
)
ax+by=b\times x1+(a\%b)\times y1=b\times x1+(a-[a/b]\times b)\times y1=a\times y1+b\times (x1-[a/b]\times y1)
ax+by=b×x1+(a%b)×y1=b×x1+(a−[a/b]×b)×y1=a×y1+b×(x1−[a/b]×y1)
得
x
=
y
1
,
y
=
x
1
−
[
a
/
b
]
×
y
1
x=y1,y=x1-[a/b]\times y1
x=y1,y=x1−[a/b]×y1
所以,基于这样的操作,我们的递归代码就产生了~
int work(int a,int b,int &x,int &y)
{
if(b==0) {x=1,y=0;return a;}
int d=work(b,a%b,x,y);
int z=x;x=y,y=z-a/b*y;
return d;
}
此时的
x
,
y
x,y
x,y就是该方程的一组特解
了。
有了特解,我们就可以求通解了。
该公式为:
x
=
x
0
×
(
c
/
d
)
+
k
×
(
b
/
d
)
,
y
=
y
0
×
(
c
/
d
)
−
k
×
(
a
/
d
)
x=x_0\times (c/d)+k\times (b/d),y=y_0\times (c/d)-k\times (a/d)
x=x0×(c/d)+k×(b/d),y=y0×(c/d)−k×(a/d)
注意这里符号是一加一减
,具体证明…你不需要知道【滑稽】其实是我不会
经典例题:
天平称重
容斥原理
这个和数学鲍姐讲的一样,不多说了,感觉这道题的思想很像容斥 codeforces,C题
欧拉函数
定义:
对正整数n,欧拉函数是小于或等于n的数中与n互质的数的数目。
性质:
(1)
φ
(
1
)
=
1
φ(1)=1
φ(1)=1
(2)
φ
(
p
)
=
p
−
1
φ(p)=p-1
φ(p)=p−1
(3)
φ
(
p
k
)
=
p
k
−
p
k
−
1
=
(
p
−
1
)
p
k
−
1
φ(p^k)=p^k-p^{k-1}=(p-1)p^{k-1}
φ(pk)=pk−pk−1=(p−1)pk−1
(4)欧拉函数是积性函数——若m,n互质,则
φ
(
m
n
)
=
φ
(
m
)
φ
(
n
)
φ(mn)=φ(m)φ(n)
φ(mn)=φ(m)φ(n)。(若函数是完全积性函数,则不需要互质条件)
(5)对于任意
n
=
p
1
k
1
×
p
2
k
2
×
.
.
.
×
p
r
k
r
n=p_1^{k_1}\times p_2^{k_2}\times ...\times p_r^{k_r}
n=p1k1×p2k2×...×prkr (其中
p
1
,
p
2
,
.
.
.
,
p
k
p_1,p_2,...,p_k
p1,p2,...,pk 为
n
n
n 的互不相同的质因子) 则有
φ
(
n
)
=
∏
i
=
1
r
φ
(
p
i
k
i
)
=
∏
i
=
1
r
(
p
i
−
1
)
p
i
k
i
−
1
=
∏
i
=
1
r
(
1
−
1
p
i
)
×
p
i
k
i
=
n
×
∏
i
=
1
r
(
1
−
1
p
i
)
\varphi(n)=\prod_{i=1}^{r}\varphi(p_i^{k_i})=\prod_{i=1}^{r}(p_i-1)p_i^{k_i-1}=\prod_{i=1}^{r}(1-\frac 1{p_i})\times p_i^{k_i}=n\times \prod_{i=1}^{r}(1-\frac 1{p_i})
φ(n)=∏i=1rφ(piki)=∏i=1r(pi−1)piki−1=∏i=1r(1−pi1)×piki=n×∏i=1r(1−pi1)
这第五条性质简直太妙了,于是我们就可以用这个去求欧拉函数了( N \sqrt N N)
long long oula(int x)
{
ans=x;
for(int i=2;i<=sqrt(x);i++)
{
if(x%i==0)
{
ans=ans/i*(i-1);//这里用到了
while(x%i==0) x/=i;
}
}
if(x>1) ans=ans/x*(x-1);//防止n为质数
return ans;
}
这个模板有没有很眼熟?没错,和埃氏筛很像,也就是说可以边筛素数边求欧拉函数,双倍的快乐【滑稽】
当然,更快的还是线性筛(
N
N
N)
void oula(int x)
{
for(int i=2;i<=x;i++)
{
if(!f[i]) p[++cnt]=i,F[i]=i-1;
for(int j=1;j<=cnt&&i*p[j]<=x;j++)
{
f[i*p[j]]=1;
if(i%p[j]==0) {F[i*p[j]]=F[i]*(p[j]);break;}
else F[i*p[j]]=F[i]*(p[j]-1);
}
}
}
这样就成功起飞了【芜湖】
逆元
定义什么的只可意会不可言传,主要用法就是在除一个数的时候,要求取模,那么就可以用$\times $他的逆元来代替。
对于逆元的求解方法才是我们应该掌握的
1.拓展欧几里得(适用于a,b互质的情况下,代码太麻烦,不常用)
依然是上面给出的代码,
x
x
x即为所求的逆元
2.费马小定理(好写,不出错,
l
o
g
N
logN
logN)
a
p
−
1
≡
1
(
%
p
)
a^{p-1}\equiv 1(\%p)
ap−1≡1(%p)
同时
/
a
/a
/a就能得到
a
p
−
2
≡
a
−
1
(
%
p
)
a^{p-2}\equiv a^{-1}(\%p)
ap−2≡a−1(%p)
这样通过快速幂就能求出逆元了(大爱这种方法,但是多次询问时可能超时)
欧拉定理
若n,a为正整数,且n,a互质,即gcd(n,a)=1,则
a
φ
(
n
)
≡
1
(
%
p
)
a^{φ(n)}\equiv 1(\%p)
aφ(n)≡1(%p)
可以看出,费马小定理是欧拉函数的一种特殊情况
一些比较恶心的推论:
a
b
≡
a
b
%
φ
(
n
)
%
n
a^b\equiv a^{b\%φ(n)} \%n
ab≡ab%φ(n)%n
a
,
n
a,n
a,n不一定互质时,
a
b
≡
a
b
%
φ
(
n
)
(
%
n
)
a^b\equiv a^{b\%φ(n)} (\%n)
ab≡ab%φ(n)(%n)
中国剩余定理
韩信点兵大家都知道,这个就是解决类似的问题,只不过更快【滑稽】,但是也更麻烦。
【例题】存在一个数x,除以3余2,除以5余3,除以7余2,然后求这个数
首先假如我们求出这样三个数k1,k2,k3,满足k1与3互质且是5和7的倍数,k2与5互质且是3和7的倍数,k3与7互质且是3和5的倍数,那么容易意会得到,
k
1
×
2
+
k
2
×
3
+
k
3
×
2
k1\times 2+k2\times 3+k3\times 2
k1×2+k2×3+k3×2一定会是一个满足题
目条件的数。而题目的通解可表示为这个数每次都加上3,5,7的最小公倍数
首先我们求出3,5,7的lcm=105
然后我们令:
x 1 = 105 / 3 = 35 x1=105/3=35 x1=105/3=35, x 2 = 105 / 5 = 21 x2=105/5=21 x2=105/5=21, x 3 = 105 / 7 = 15 x3=105/7=15 x3=105/7=15
然后我们求解以下方程: ( ( a × x 1 ) (a\times x_1) (a×x1)就是 k 1 k_1 k1)
a × x 1 % 3 = 1 a\times x1\%3=1 a×x1%3=1, b × x 2 % 5 = 1 b\times x2\%5=1 b×x2%5=1, c × x 3 % 7 = 1 c\times x3\%7=1 c×x3%7=1
这个格式的式子好眼熟,用扩展欧几里德求a=2,b=1,c=1。
答案就是:
a n s = ( a × x 1 × 2 + b × x 2 × 3 + c × x 3 × 2 ) % l c m = 23 ans=(a\times x1\times 2+b\times x2\times 3+c\times x3\times 2)\%lcm=23 ans=(a×x1×2+b×x2×3+c×x3×2)%lcm=23
通过以上例题,我们可以总结出求解的基本步骤
1.求出除数的乘积(不是最小公倍数没关系,这样方便)
2.循环,每次用公倍数除以除数,把得到的数当做
a
a
a,将除数当做
b
b
b
3.运用扩展欧几里得求解出x,
a
n
s
ans
ans加上
a
×
x
×
余
数
a\times x\times 余数
a×x×余数,然后再模公倍数
那么来看看代码吧
for(int i=1;i<=n;i++)
{
long long x,y;
long long aa=sum/a[i];
ojld(aa,a[i],x,y);
ans=(x*aa%sum*b[i]%sum+ans)%sum;
}
高斯消元( n 3 n^3 n3)
上三角五步走:
int t=r;
for(int i=r;i<=n;i++) if(fabs(a[i][c])>fabs(a[t][c])) t=i;//找当前这一列绝对值最大的数
if(fabs(a[t][c])<py) continue;//如果这列全是零了,就不管
for(int i=c;i<=n+1;i++) swap(a[t][i],a[r][i]);//把他换到没被锁定的第一行
for(int i=n+1;i>=c;i--) a[r][i]/=a[r][c];//把这个数变成1,为了维护等式这一行都要除这个数
for(int i=r+1;i<=n;i++) if(fabs(a[i][c])>py) for(int j=n+1;j>=c;j--) a[i][j]-=a[r][j]*a[i][c];
//把每行的第c个数都消为0
//第二层循环倒着写,因为如果先把第一个变成0了,那后面的运算都要依靠第一个值,会出错
r++;
线性基
简单来说是一些数的集合,而这些数通过异或可以构成的数都不在这个集合中。毕竟是“基”,基础嘛,肯定是最最最最最下面的东西,没有更小的了,也就是没有数通过变换能构成他们
有关更详细 的介绍和一些经典代码的写法,参见
点我点我(作者:Hypoc_)
卡特兰数(设为h(n))
- 通项公式: h ( n ) = 1 / ( n + 1 ) × C 2 n n h(n)=1/(n+1)\times C^n_{2n} h(n)=1/(n+1)×C2nn
- 递推式子: h ( n + 1 ) = ( 4 n + 2 ) / ( n + 2 ) × h ( n ) h(n+1)=(4n+2)/(n+2)\times h(n) h(n+1)=(4n+2)/(n+2)×h(n)
FWT
简单来说看下图
行列式
性质
- 交换对应矩阵的 2 行(列),行列式取反
- 交换 1 行与 1 列(进行一次矩阵转置),行列式不变
- 行列式的行(列)所有元素等比例变化,则行列式也等比例变化
- 如果行列式对应矩阵 A 中有一行(列),是对应 2 个矩阵 B,C 中分别的 2 行(列)所有元素之和。那么有 A=B+C
- 如果一个矩阵存在两行(列)成比例则 A的值=0
- 把一个矩阵的一行(列)的值全部乘一个常数加到另一行(列)上,行列式值不变
求值
初始为
∣
2
5
∣
|\frac{2}{5}|
∣52∣
使第二行除以(5/2)
∣
2
1
∣
|\frac{2}{1}|
∣12∣
交换这两行
∣
1
2
∣
|\frac{1}{2}|
∣21∣
第二行减第一行的两倍
∣
1
0
∣
|\frac{1}{0}|
∣01∣
交换
∣
0
1
∣
|\frac{0}{1}|
∣10∣
一直做下去就好啦
int work()
{
int w=1;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
while(a[i][i])
{
int tmp=a[j][i]/a[i][i];
for(int k=i;k<=n;k++) a[j][k]=((a[j][k]-1ll*tmp*a[i][k])%p+p)%p;
swap(a[i],a[j]),w=-w;
}
swap(a[i],a[j]),w=-w;
}
}
for(int i=1;i<=n;i++) w=1ll*w*a[i][i]%p;
return (w%p+p)%p;
}
矩阵求逆
我们现在要求
A
A
A的逆矩阵
A
−
1
A^{-1}
A−1
设
E
E
E为单位矩阵,也就是对角线上的数都是1,其他是0
那么根据定义
A
×
A
−
1
=
E
,
E
×
A
−
1
=
A
−
1
A\times A^{-1}=E,E\times A^{-1}=A^{-1}
A×A−1=E,E×A−1=A−1
所以我们将
A
A
A变成
E
E
E的过程中,对另一个
E
E
E也做同样的操作(这两个
E
E
E肯定不同呀),于是后面的那个
E
E
E就被我们变成
A
−
1
A^{-1}
A−1了