目录
高密度计算
概念
指参与运算的数大大超出了标准数据类型所能表示的范围的运算
c/c++中,最大数据类型只有64位,如果需要处理更大的数,只能用数组来模拟,把大数的每一位存储在数组中
java中,可以直接计算。有两个类:BigInteger 和 BigDecimal,分别表示大整数类和大浮点数类,存在于 java.math.* 包中(java虽然能处理大数,但对于规模过大的问题,java也不能做)
例如:hdu 1024题:输入整数 N(0<=N<=10000),输出 N!
java代码如下:
import java.math.BigInteger;
import java.util.Scanner;
public static void main(String args[]){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int n = sc.nextInt();
BigInteger ans = BigInteger.ONE;
for(int i=1; i<=n; i++){
ans = ans.multiply(BigInteger.valueOf(i));
}
System.out.println(ans);
}
sc.close();
}
数论
研究整数性质的数学分支
模运算
定义模运算:a 除以 m 的余数,记为:a mod m = a % m
取模结果满足 0<= a mod m <=m-1
性质:加:(a+b) mod m = ((a mod m) + (b mod m)) mod m
减:(a-b) mod m = ((a mod m) - (b mod m)) mod m
乘:(a*b) mod m = ((a mod m) * (b mod m)) mod m
除:,其中 k 是 b 的逆元
快速幂
快速幂就是高效地算出 :先算,然后继续计算平方,一直算到 n 次幂。复杂度为
代码如下:
int fastPow(int a, int n){
if(n==1) return a;
int temp = fastPow(a,n/2); //分治
if(n%2 == 1){
return temp*temp*a; //奇数个a
}else //偶数个a
return temp*temp;
}
还有一种方法:用位运算处理快速幂,复杂度也是。以为例,说明:
先把分解成、、的乘积,而 、、,都是2的倍数,逐级递推就可。那么如何将 n 分解成 11=8+2+1 这样的倍乘关系?用二进制就可解决:把 n 转换为二进制数,二进制数中每一位的权值都是低一位的两倍,对应的是倍乘的关系。另外,还有一个需要处理的问题:如何跳过那些不需要的?这里只需要判断二进制位是否为0.(0就是需要跳过的),利用二进制的位运算是很容易实现:
-
(n&1)==1,取 n 的最后一位,并且判断这一位是否需要跳过
- n>>=1,把 n 右移一位,目的是把刚处理过的 n 的最后一位去掉
java代码如下:
int fastPow(int a, int n){
int base = a; //直接用a计算也行
int res = 1; //用res返回结果
while (n!=0){
if((n&1)==1){ //如果n的最后一位是1,表示这个地方需要乘
res *= base;
}
base *= base; //推算乘积:a^2 -> a^4 -> a^8 ...
n >>= 1; //n右移一位,把刚处理过的n的最后一位去掉
}
return res;
}
n | res(res *= base) | base(base *= base) | |
第1轮 | 1011 | ||
第2轮 | 101 | ||
第3轮 | 10 | 是0,res不变 | |
第4轮 | 1 | ||
结束 | 0 |
快速幂取模
性质:
矩阵快速幂
给定一个 m*m 的矩阵 A,求它的 n 次幂
算法:将矩阵当作变量来操作。首先要定义矩阵的结构体,并且定义矩阵相乘的操作
java代码如下:
final int MAXN = 2; //矩阵的阶
final int MOD = (int)1e4; //根据题目定义的模
class Matrix{ //定义矩阵
int[][] p;
public Matrix(){
this.p = new int[MAXN][MAXN];
}
}
Matrix Multi(Matrix a, Matrix b){ //矩阵的乘法
Matrix res = new Matrix();
for(int i=0; i<MAXN; i++)
for (int j=0; j<MAXN; j++)
for(int k=0; k<MAXN; k++){
res.p[i][j] = (res.p[i][j] + a.p[i][k] * b.p[k][j]) % MOD;
}
return res;
}
下面是矩阵快速幂的java代码(复杂度:矩阵乘法复杂度:,快速幂的复杂度:,所以总复杂度为:):
Matrix fastm(Matrix a, int n){
Matrix res = new Matrix();
for(int i=0; i<MAXN; i++) //初始化为单位矩阵,相当于前面所说的“int res=1;”
res.p[i][i] = 1;
while (n>0){
if((n&1)!=0)
res = Multi(res,a);
a = Multi(a,a);
n >>= 1;
}
return res;
}
最大公约数 GCD
整数 a 和 b 的最大公约数记为:gcd(a,b)
经典的欧几里得算法,代码如下:
int gcd(int a,int b){
return b == 0 ? a : gcd(b,a%b);
}
最小公倍数 LCM
整数 a 和 b 的最小公倍数记为:lcm(a,b)
代码如下:
int lcm(int a, int b){
return a/gcd(a,b)*b;
}
扩展欧几里得算法与二元一次方程的整数解
问题:给出整数a、b、n,问方程 ax+by=n 什么时候有整数解?如何求所有的整数解?
有解的充分必要条件:gcd(a,b) 可以整除 n。简单解释如下:
令 a=gcd(a,b)a'、b=gcd(a,b)b',有 ax+by=gcd(a,b)(a'x+b'y)=n;如果x、y、a'、b'都是整数,那么 n 必须是 gcd(a,b) 的倍数才有整数解
如果确定有解,一种解题方法是先找到一个解(x0, y0),那么通解公式如下:
x = x0 + bt,
y = y0 - at, t 是任意整数
所以,问题转化为如何求 (x0,y0)。利用扩展欧几里得算法可以求出这个特解
扩展欧几里得算法
当方程符合 ax + by = gcd(a,b) 时,可以用扩展欧几里得算法求 (x0,y0). java代码如下:
int x,y; //x,y作为成员变量,通过以下方法即可得到一个特解(x0,y0)
void extend_gcd(int a,int b){
if(b==0){
x=1;
y=0;
return;
}
extend_gcd(b,a%b);
int temp = x;
x = y;
y = temp - (a/b) * y;
}
程序运行理解可如下:
求任意方程 ax+by=n 的一个整数解
步骤如下:
- 判断方程 ax+by=n 是否有整数解,有解的条件是 gcd(a,b) 可以整除 n
- 用扩展欧几里得算法求 ax+by=gcd(a,b) 的一个解 (x0,y0)
- 在 ax+by=gcd(a,b) 两边同时乘以 n/gcd(a,b),得:
- 对照 ax+by=n,得到它的一个解(x0',y0')是:
扩展欧几里得算法应用场合
- 求解不定方程
- 求解模的逆元
- 求解同余方程
同余与逆元
同余
两个整数 a、b 和一个正整数 m,如果 a 除以 m 所得的余数和 b 除以 m 所得的余数相等,即 a mod m = b mod m,称 a 和 b 对于 m 同余,m 称为同余的模。也可以这样理解:m|(a-b),即 a-b 是 m 的整数倍。
同余符号即为:
一元线性同余方程
,即 ax 除以 m,b 除以 m,两者余数相同,这里 a、b、m 都是整数,求解 x 的值
方程也可以这样理解:ax-b 是 m 的整数倍。设 y 是倍数,那么 ax-b=my,移项得到 ax-my=b。因为 y 可以是负数,改写成 ax+my=b,这就是在扩展欧几里得算法中提到的二元一次不定方程
当且仅当 gcd(a,m)能整除 b 时有整数解。
当 gcd(a,m)=b 时,可以直接用扩展欧几里得算法求解 ax+my=b
如果不满足gcd(a,m)=b,还能用扩展欧几里得算法求解 ax+my=b 吗?答案是肯定的,但是需要结合下面的逆元
逆元
给出 a 和 m,求解方程 ,即 ax 除以 m 的余数是 1
该问题等价于求解 ax+my=1,可以用扩展欧几里得算法求解。
方程 的一个解 x,称 x 为 a 模 m 的逆
求逆元的代码如下:
int x,y; //结合上述的扩展欧几里得算法 extend_gxd
int mod_inverse(int a,int m){
extend_gcd(a,m);
return (m + x%m) % m; //x 可能是负数,需要处理
}
逆元与除法取模
见上述模运算中的除法
逆元与求解二元一次方程 ax+my=b
如果得到了 a 的逆,可以来求解形如 的任何同余方程。方法如下:
令 a' 是 a 模 m 的逆,则 ;在 的两边同时乘以 a',得到 ,所以
理解:因为 a' 是 a 的逆元,由模运算的除法性质可得
利用逆元求解二元一次方程:
步骤 | 求解方程 ax+my=b 同余方程是 | 例:求解 8x+31y=24 同余方程是 |
1 | 有解的条件:gcd(a,m)能整除 b | gcd(8,31)=1 能整除 24 |
2 | 求的逆元 a',等价于用扩展欧几里得算法求解ax+my=1 | 8x+31y=1 的一个解是 x=4,y=-1 即一个逆元是 a'=4 |
3 | 一个特解是 x=a'b | x=a'b=4*24=96 |
4 | 代入方程 ax+my=b,求解 y | 代入8x+31y=24,得到 y = -24 |
对于 x=a'b 的理解: ,两边同乘b,,又 ,对比得
素数
试除法判断素数
给定 n,如果它不能整除 内的所有数,它就是素数。证明如下:
设 n=a*b ,有 , 令 。只要检查 内的数,如果 n 不是素数,就能找到一个 a。如果不存在这个 a,那么 内也不存在 b
以上判断的范围可以再缩小: 内的所有素数
用试除法判断素数,复杂度是 ,对于 的数是没有问题的
java代码如下:
boolean is_prime(int n){
if(n<=1) return false;
for(int i=2; i*i<=n; i++){ //比这样写更好:for(int i=2;i<=Math.sqrt(n);i++)
if(n % i == 0)
return false;
}
return true;
}
用埃式筛法求素数的数量
一个与素数有关的问题:求 [2,n] 内所有的素数
对于初始队列 {2,3,4,5,6,7,8,9,10,11,...,n},操作步骤如下:
- 输出最小的素数2,然后筛掉 2 的倍数,剩下 {3,5,7,9,11,...}
- 输出最小的素数3,然后筛掉 3 的倍数,剩下 {5,7,11,13,...}
- 输出最小的素数5,然后筛掉 5 的倍数,剩下 {7,11,13,...}
继续以上步骤,直到队列为空
下面是java程序,其中 visit[i]=true,表示它被筛掉了,不是素数
final int MAX = (int)1e7; //定义空间大小
int[] prime = new int[MAX]; //存放素数,记录 visit[i]=false 的项
boolean[] visit = new boolean[MAX]; //true 表示被筛掉,不是素数
int E_sieve(int n){ //埃式筛法,计算 [2,n] 内的素数
for(int i=0;i<=n;i++)
visit[i] = false; //初始化
for (int i=2;i*i<=n;i++) //筛掉非素数
if(!visit[i])
for (int j=i*i;j<=n;j+=i)
visit[j] = true;
int k=0; //统计素数的个数
for (int i=2;i<=n;i++)
if(!visit[i])
prime[k++] = i; //存储素数
return k;
}
埃式筛法应用于大区间素数
如果把 [2,n] 看成一个区间,那么可以把埃式筛法扩展到求区间 [a,b] 的素数,,
根据埃式筛法很容易理解试除法判断素数的原理: 内的非素数 b 肯定对应一个比它小的素数 a。在用试除法的时候,如果 n 能整除 a,已经证明了 n 不是素数,b 就不用再试了
这个原理可以用来理解大区间求素数问题。先用埃式筛法求 内的素数,然后用这些素数来筛 区间的素数即可
- 计算复杂度:
- 空间复杂度:需要定义两个数组,一个用于处理 内的素数,另一个用于处理 内的素数,空间复杂度为: