程序设计中的数学
符号约定
- 整除:\(a|b\),即 \(b\) 整除于 \(a\),或 \(a\) 为 \(b\) 的因子。
- 互质:\(m⊥n\),即 \(gcd(m,n)=1\)。
- 取整:\(\lceil x \rceil\) 向上取整,\(\lfloor x \rfloor\) 向下取整,\([x]\) 向零取整。
- 同余:\(a \equiv b \pmod{m}\),即 \(a,b\) 在模 \(m\) 的意义下同余。
- 累加求和:\(\sum_{i=1}^{n}i = 1+2+3+...+n\)。
- 累乘求积:\(\prod_{i=1}^{n}i = 1*2*3*...*n\)。
奇数偶数
问题:求 \(1,2,3,...,n\) 的奇数和,其中 \(n≤2^{32}\)。
等差数列:当首项为 \(a_1\),末项为 \(a_n\),项数为 \(n\),其前 \(n\) 项和 \(s=\frac{(a_1+a_n)* n}{2}\)
分析:循环肯定TLE,明显存在规律,有\(n\) 为奇数和偶数时有不同情况,分情况讨论;
-
当 \(n\) 是奇数的时候,\(s=1+3+5+...+n;\)
假设有 \(k\) 项元素,则 \(1+(k-1)*2=n\),解出 \(k=\frac{n-1}{2}+1=\frac{n+1}{2}\)
\(s=\frac{(1+n)*k}{2}=\frac{(n+1)^2}{4}\)
在程序中 \(2^{64}\) 会溢出,所以可以写成 \(((n+1)/2) * ((n+1)/2)\). -
当 \(n\) 是偶数的时候,\(s=1+3+5+...+n-1;\)
假设有 \(k\) 项元素,则 \(1+(k-1)*2=n-1\),解出 \(k=\frac{n-1-1}{2}+1=\frac{n}{2}\)
\(s=\frac{(1+n-1)*k}{2}=\frac{(n)^2}{4}\)
在程序中 \(2^{64}\) 会溢出,所以可以写成 \((n/2) * (n/2)\)
算术平方根
\(x*x=n\),也可以记为 \(x=\sqrt{n}\),其中\(\sqrt{n}\) 就被称为对 \(n\) 开根号。
\(x\) 就被称为 \(n\) 的算术平方根。
当给定 \(n\),如何准确的求解 \(x\) 呢?
- 牛顿迭代法
可以假定一个解 \(x_0\),那么可以得到另外一个解:\(x_1=n/x_0\),其中 \(x_0*x_1=n\)。
我们可以取两个解的平均值作为新解,再按照同样的方式继续推导满足误差的解。
于是得到迭代公式:\(x=(x+n/x)/2\);
- 二分法
确定答案区间,二分区间,直到满足误差的解。
double sqrt1(double n){
double x=n/2;
while(fabs(x*x-n) > eps) x = (x+n/x)/2;
return x;
}
double sqrt12(double n){
double x=n/2;
for(int i=1; i<=100; i++) x = (x+n/x)/2;
return x;
}
double sqrt2(double n){
double l=0, r=n/2;
while(r-l > eps){
double mid=(l+r)/2;
if(mid*mid >= n) r=mid;
else l=mid;
}
return r;
}
素数
定义:大于 \(1\) 的自然数中,因子只有 \(1\) 和其本身的数,称为素数或质数。
素数判定:若 \(i|n\),则 \(\frac{n}{i}|n\),所以只需要检测 \(i \in [2,\sqrt{n}]\) 是否满足 \(i|n\) 即可,复杂度 \(O(\sqrt{n})\)。
由于 \(i*i\leq n\) 容易爆 \(int\),改写为除法形式 \(i\leq n/i\)。
bool isprime(int n){
for(int i=2; i<=n/i; i++) if(n%i==0) return 0;
return n>1;
}
朴素筛法,原理:一个数的倍数一定不是素数,时间复杂度 \(O(nlogn)\)
埃式筛法,原理:素数的倍数一定不是素数+算数基本定理,时间复杂度 \(O(nloglogn)\)
欧拉筛法,原理:每一个数都具有唯一最小素因子,时间复杂度 \(O(n)\)。
const int N=1e6+10;
int st[N],primes[N],pn=0; // st[i]=1 表示i被筛除
void simple_sieve(int n){ // 普通筛法 O(nlogn)
st[0] = st[1] = 1;
for(int i=2; i<=n; i++){
if(!st[i]) primes[++pn]=i;
for(int j=2; j<=n/i; j++) st[i*j]=1;
}
}
void eratosthenes_sieve(int n){ // 埃式筛法 O(nloglogn)
st[0] = st[1] = 1;
for(int i=2; i<=n; i++){
if(!st[i]){
primes[++pn]=i;
for(int j=2; j<=n/i; j++) st[i*j]=1;
}
}
}
void euler_sieve(int n){ // 欧拉筛法 O(n)
st[0] = st[1] = 1;
for(int i=2; i<=n; i++){
if(!st[i]) primes[++pn]=i;
for(int j=1; primes[j]<=n/i; j++){
st[primes[j]*i]=1;
if(i%primes[j]==0) break; // 最小素因子
}
}
}
约数
质因数分解:算术基本定理,唯一分解定理
定义:一个大于 \(1\) 的正整数可以唯一分解为若干个质数的乘积。
记:\(N=\prod_{i=1}^k p_i^{ai} = p_1^{a_1}*p_2^{a_2}...*p_k^{a_k}\),其中 \(p_1<p_2<...<p_k\), \(p_i\) 是质数。
约数个数定理:\(num=\prod_{i=1}^{k}(a_i+1)=(a_1+1)(a_2+1)...(a_k+1);\)
约数和定理:\(sum=\prod_{i=1}^{k}\sum_i^{k}p_i^{a_k}=(p_1^0+p_1^1+…p_1^{a_1})(p_2^0+p_2^1+…p_2^{a_2})…(p_k^0+p_k^1+…p_k^{a_k});\)
- 证明:
\(p_1^{a_1}\) 的约数有:\(p_1^0, p_1^1,p_1^2,...,p_1^{a_1}\),共 \(a_1+1\) 个;
\(p_2^{a_2}\) 的约数有:\(p_2^0, p_2^1,p_2^2,...,p_2^{a_2}\),共 \(a_2+1\) 个;
\(p_k^{a_k}\) 的约数有:\(p_k^0, p_k^1,p_k^2,...,p_k^{a_k}\),共 \(a_k+1\) 个;
根据乘法原理
\(num=\prod_{i=1}^{k}(a_i+1)=(a_1+1)(a_2+1)...(a_k+1);\)
\(sum=\prod_{i=1}^{k}\sum_i^{k}p_i^{a_k}=(p_1^0+p_1^1+…p_1^{a_1})(p_2^0+p_2^1+…p_2^{a_2})…(p_k^0+p_k^1+…p_k^{a_k});\)
map<int,int> prime_factorization(int m){// 质因数分解
map<int,int> mp;
for(int i=2; i<=m/i; i++){
if(m%i==0){
int num=0;
while(m%i==0) m/=i, num++;
mp[i]=num;
}
}
if(m>1) mp[m]=1;
return mp;
}
LL num(map<int,int> m){
LL res=0;
for(auto u:m) res=res*(u.second+1)%mod;
return res;
}
LL sum(map<int,int> m){
LL res=1;
for(auto u:m){
LL a=u.first, b=u.second, t=1;
while(b--) t=(t*a+1)%mod;
res = res*t%mod;
}
return res;
}
欧几里德算法/辗转相除法
原理:\(a,b\) 的最大公约数等于 \(b,a\%b\) 的最大公约数。
记作:\(gcd(a,b)=gcd(b,a\%b), gcd(a,0)=a.\)
- 证明:
int gcd(int a,int b){
return b ? gcd(b,a%b):a;
}
更相减损术
原理:\(a,b\) 的最大公约数等于 \(b,a-b\) 的最大公约数 \((a>b)\)。
记作:\(gcd(a,b)=gcd(b,a-b), gcd(a,0)=a.\)
- 证明:
对于 \(a,b\) 的任意公约数 \(d\),有 \(d|a、d|b、d|(a-b)\),所以 \(d\) 也是 \(b,a-b\) 的公约数。
则 \(a,b\) 的公约数与 \(b,a-b\) 的公约数集合相同,于是最大公约数也相同。
也可以换等式证明:gcd(a,b)=gcd(a,a-b)
设a=k1*d,b = k2*d,则gcd(a, b)=d,gcd(k1, k2)=1,(a>b);
则a-b=k1*d-k2*d=(k1-k2)*d;
则gcd(b, a-b) = d = gcd(a, b);
int gcd(int a,int b){
if(a<b) swap(a,b);
return b ? gcd(b,a-b):a;
}
最小公倍数
定理:两个数的乘积等于它们最大公约数与最小公倍数的乘积。
证明:a*b=gcd(a,b)*lcm(a,b)
gcd(a,b) 表示a,b的最大公约数;
lcm(a,b) 表示a,b的最小公倍数;
令 a=k1*d, b=k2*d, 则:gcd(a,b)=d;
又lcm(a,b)=k1*k2*d(k1,k2互质)
则得证:a*b= gcd(a,b)*lcm(a,b).
int lcm(int a,int b){
return a/gcd(a,b)*b;
}
扩展欧几里得
原理:对于给定的两个整数 \(a,b\),必存在整数 \(x,y\),使得 \(ax+by=gcd(a,b)\)。
int exgcd(int a,int b,int &x,int &y){
if(!b){ x=1, y=0; return a; }
int d=exgcd(b,a%b, y, x);
y -= a/b*x;
return d;
}
欧拉函数
\(1∼N\) 中与 \(N\) 互质的数的个数被称为欧拉函数,记为 \(\varphi(N)\)。
\(N=p_1^{a_1}p_2^{a_2}…p_k^{a_k}\),\(\varphi(N)=N(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_k})\)
- 证明
欧拉函数是一个积性函数,当 \(m,n\) 互质时,\(φ(mn)=φ(m)∗φ(n)\)。
根据唯一分解定理知 \(N=p_1^{a_1}p_2^{a_2}…p_k^{a_k}\),其中 \(p_1<p_2<...<p_k\), \(p_i\) 是质数。
则 \(φ(N)=φ(p_1^{a_1})∗φ(p_2^{a_2})...*φ(p_k^{a_k})\);
与 \(p_i^{a_i}\) 不互质的数有 \(p_i,2p_i,3p_i,...,p_i^{a_i-1}p_i\) 共 \(p_i^{a_i-1}\) 个,即 \(φ(p_i^{a_i})=p_i^{a_i}-p_i^{a_i-1}\)。
int euler(int m){
int res=m;
for(int i=2; i<=m/i; i++){
if(m%i==0){
while(m%i==0) m/=i;
res=res/i*(i-1);
}
}
if(m>1) res=res/m*(m-1);
return res;
}
欧拉筛法求欧拉函数
质数 \(i\) 的欧拉函数即为 \(phi[i]=i-1\)。
\(phi[primes[j]*i]\) 分为两种情况:
① \(i\%primes[j]==0\):\(primes[j]\) 是 \(i\) 的最小素因子,也是 \(primes[j]*i\) 的最小质因子,因此 \(1 - \frac{1}{primes[j]}\) 在 \(phi[i]\) 中计算过了,结果为 \(phi[i] * primes[j]\)。
② \(i \% primes[j] != 0\):\(primes[j]\) 不是 \(i\) 的质因子,只是 \(primes[j]*i\) 的最小质因子,需要补上\(1-\frac{1}{primes[j]}\) 这一项,结果 \(phi[i]*primes[j]*(1-\frac{1}{primes[j]})=phi[i]*(primes[j]-1)\)。
void euler_sieve(int n){ // 欧拉筛法 O(n)
phi[1]=1;
for(int i=2; i<=n; i++){
if(!st[i]) primes[++pn]=i, phi[i]=i-1;
for(int j=1; primes[j] <=n/i; j++){
st[primes[j]*i]=1;
if(i%primes[j]==0) {
phi[primes[j]*i]=phi[i]*primes[j];
break; // 最小素因子
}
phi[primes[j]*i]=phi[i]*(primes[j]-1);
}
}
}
裴蜀定理
裴蜀定理:方程 \(ax+by=c\) 有整数解的充要条件是 \(gcd(a,b)|c\)。
同余方程
快速幂
求 \(a^n \ mod \ p\)。
两种思路:二分递归、进制原理
二分递归证明如下:
当 \(n\) 为偶数的时候:\(a^{n} = a^{n/2} * a^{n/2}\)
当 \(n\) 为奇数的时候:\(a^n = a^{n/2} * a^{n/2} * 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
除法不满足,需要使用逆元求解
这里我们选择第一个进行证明。
由于减法其实就是加上一个负数,乘法就是多个加法,所以也可以伪证另外两条性质。
注意:快速幂运算是呈几何倍数的增加,容易爆范围,开 long long。
LL halfpow(LL a, LL n, LL p){
if(n==0) return 1;
LL ans=halfpow(a, n/2);
ans = ans*ans%p;
if(n&1) ans=ans*a%p;
return ans;
}
-
二进制原理实现快速幂(二分快速幂思想不变,但是从进制原理的角度去理解)
-
\(a^{n} = a^{XB}\)
-
\(a^{11} = a^{8+2+1} = a^{2^{3} +2^{1} + 2^{0}} = a^{1011B}\)
发现仅当二进制位为 \(1\) 时,才会对其进行累乘,而累乘的数值为该进制位的权值次幂(基数)。
比如:\(1011\),从右向左,仅当第 \(0\) 位,第 \(1\) 位,第 \(3\) 位时才进行累乘计算。
算法流程:
初始值:ans=1, a=a, X=1011
如果X的二进制最低位为1,即 lowbit(X)=1,则 ans = ans*a;
基数一直: a = a*a;
主要在于:a = a*a 的理解
次数:数值
0:a = a^(2^0)
1:a^2 = a^(2^1)
2:a^4 = a^(2^2)
3:a^8 = a^(2^3)
4:a^16 = a^(2^4)
LL fastpow(LL a, LL n, LL p){
LL ans=1;
while(n){
if(n&1) ans=ans*a%p;
a = a*a%p;
n >>= 1; // n/=2;
}
return ans;
}
费马小定理:如果 \(p\) 是一个质数,而整数 \(a\) 不是 \(p\) 的倍数,则有 \(a^{p-1}≡1(mod\ p)\)
乘法逆元
若整数 \(b,m\) 互质,并且对于任意的整数 \(a\),如果满足 \(b|a\),则存在一个整数 \(x\),使得 \(a/b≡ax(mod\ m)\),则称 \(x\) 为 \(b\) 的模 \(m\) 乘法逆元,记为 \(b^{−1}(mod\ m)\)。
\(b\) 存在乘法逆元的充要条件是$ b$ 与模数 \(m\) 互质。
由费马小定理可知,\(a*a^{p-2}≡1(mod\ p);\)
当模数 \(m\) 为质数时,\(b^{m−2}\%p\) 即为 \(b\) 的乘法逆元,可以用快速幂求解。
当模数 \(m\) 不是质数时,可以用扩展欧几里得算法求逆元:
\(a\) 有逆元的充要条件是 \(a,p\) 互质,即 \(gcd(a,p)=1\)
假设 \(a\) 的逆元为 \(x\), 则 \(ax≡1(mod \ p)\),等价于:\(ax+py=1\)
\(exgcd(a,p,x,y),\quad x=(x+p)\%p\)