算法笔记 第5章 入门篇(3)——数学问题

记一点笔记

5.1 简单数学

5.2 最大公约数和最小公倍数

5.2.1 最大公约数

int gcd(int a, int b) {
	if(b == 0) return a;
	else return gcd(b, a % b);
}
//简洁写法
int gcd(int a, int b) {
	return !b ? a : gcd(b, a% b);
}

5.2.2 最小公倍数

int lcm(int a, int b) {
	int d = gcd(a, b);
	return a / d * b;
}

5.3 分数的四则运算

5.3.1 分数的表示和化简

1. 分数的表示

struct Fraction {
	int up, down;
};

制定三项规则:

  1. 使down为非负数
  2. 如果该分数恰为0,规定其分子为0,分母为1
  3. 分子和分母没有除了1以外的公约数

2. 分数的化简

Fraction reduction(Fraction result) {
	if(result.down < 0) {
		result.up = -result.up;
		result.down = -result.down;
	}
	if(result.up = 0) {
		result.down = 1;
	} else {
		int d = gcd(abs(result.up), abs(result.down));
		result.up /= d;
		result.down /= d;
	}
	return result;
}

5.3.2 分数的四则运算

1. 分数的加法

Fraction add(Fraction f1, Fraction f2) {
	Fraction result;
	result.up = f1.up * f2.down + f2.up * f1.down;
	result.down = f1.down * f2.down;
	return reduction(result);
}

2. 分数的减法

Fraction minu(Fraction f1, Fraction f2) {
	Fraction result;
	result.up = f1.up * f2.down - f2.up * f1.down;
	result.down = f1.down * f2.down;
	return reduction(result);
}

3. 分数的乘法

Fraction multi(Fraction f1, Fraction f2) {
	Fraction result;
	result.up = f1.up * f2.up;
	result.down = f1.down * f2.down;
	return reduction(result);
}

4. 分数除法

  • 如果除数为0(f2.up == 0),那么应当直接特判输出题目要求的输出语句(例如输出Error、Inf之类)。只有当除数不为0时,才能使用下面的函数进行计算。
Fraction divide(Fraction f1, Fraction f2) {
	Fraction result;
	result.up = f1.up * f2.down;
	result.down = f1.down * f2.up;
	return reduction(result);
}

5.3.3 分数的输出

几个注意点:

  1. 输出分数前,需要先对其进行化简
  2. 如果分数 r 的分母 down 为1,说明该分数是整数,一般来说题目会要求直接输出分数,而省略分母的输出
  3. 如果分数 r 的分子 up 的绝对值大于分母 down,说明该分数是假分数,此时应按带分数的形式输出,即整数部分为r.up/r.down,分子部分为abs(r.up)%r.down,分母部分为r.down
  4. 以上均不满足时说明分数 r 是真分数,按原样输出即可
void showResult(Fraction r) {
	r = reduction(r);
	if(r.down == 1) 
		printf("%lld", r.up);
	else if(abs(r.up) > r.down)
		printf("%d %d/%d", r.up / r.down, abs(r.up) % r.down, r.down);
	else
		printf("%d/%d", r.up, r.down);
}
  • 由于分数的乘法和出发的过程中可能使分子或分母超过int型的标识范围,因此一般情况下,分子和分母应当使用long long型来存储

5.4 素数

5.4.1 素数的判断

bool isPrime(int n) {
	if(n <= 1) return false; //特判
	int sqr = (int)sqrt(1.0 * n); //根号n
	for(int i = 2; i <= sqr; i++) {
		if(n % i == 0) return false;
	}
	return true;
}

5.4.2 素数表的获取

const int maxn = 101; //表长
int prime[maxn], pNum = 0 //prime数组存放所有素数,pNum为素数个数
bool p[maxn] = {0}; //p[i] == true表示i是素数
void Find_Prime() {
	for(int i = 1; i < maxn; i++) {
		if(isPrime(i) == true) {
			prime[pNum++] = i;
			p[i] = true;
		}
	}
}
  • 埃式筛法:从小到大枚举所有数,对每一个素数,筛去它的所有倍数,剩下的就都是素数了。
const int maxn = 101; //表长
int prime[maxn], pNum = 0 //prime数组存放所有素数,pNum为素数个数
bool p[maxn] = {0}; //p[i] == false表示i是素数
void Find_Prime() {
	for(int i = 2; i < maxn; i++) {
		if(p[i] == false) { //如果i是素数
			prime[pNum++] = i;
			for(int j = i + i; j < maxn; j += i) {
				//筛去所有i的倍数
				p[j] = true;
			}
		}
	}
}

5.5 质因子分解

  • 由于最后都要归结到若干不同素数的乘积,因此不妨先把素数表打印出来。
  • 如果有些题目中要求对 1 进行处理,那么视题目条件而定来进行特判处理。
  • 由于每个质因子都可以布置出现一次,因此不妨定义结构体factor,用来存放指引及其个数。
struct factor {
	int x, cnt; //x为质因子,cnt为其个数
} fac[10];
  • 对一个正整数 n 来说,如果它存在 [2,n] 范围内的质因子,要么这些质因子全部小于等于 sqrt(n),要么只存在一个大于 sqrt(n) 的质因子,而其余质因子全部小于等于 sqrt(n)。

质因子分解:

  1. 枚举 1 ~ sqrt(n) 范围内的所有质因子 p,判断 p 是否是 n 的因子。
//如果是,进行以下操作;如果不是,则跳过
if(n % prime[i] == 0) {
	fac[num].x = prime[i]; //记录该因子
	fac[num].cnt = 0;
	while(n % prime[i] == 0) { //计算个数
		fac[num].cnt++;
		n /= prime[i];
	}
	num++;
}
  1. 如果在上面步骤结束后 n 仍大于1,说明 n 有且仅有一个大于 sqrt(n) 的质因子(有可能是 n 本身),这时需要把这个质因子加入fac数组,并令其个数为1。
if(n != 1) {
	fac[num].x = n;
	fac[num++].cnt = 1;
}

5.6 大整数(高精度整数)运算

5.6 大整数的存储

  • 用数组存储。整数的高位存储在数组的高位,整数的地位存储在数组的地位。
  • 把整数按字符串读入的时候,需要在另存为至整数数组的时候反转一下。
struct bign {
	int d[1000];
	int len;
	bign() {  //构造函数,用以初始化结构体
		memset(d, 0, sizeof(d));
		len = 0;
	}
};
  • 输入大整数时,一般都是先用字符串读入,然后再把字符串另存为至bign结构体。
bign change(char str[]) {  //把整数转换为bign
	bign a;
	a.len = strlen(str);  //bign的长度就是字符串的长度
	for(int i = 0; i < a.len; i++) {
		a.d[i] = str[a.len - i - 1] - '0';  //逆着赋值
	}
	return a;
}
//比较a和b大小,a大、相等、a小分别返回1、0、-1
int compare(bign a, bign b) {
	if(a.len > b.len) return 1;
	else if(a.len < b.len) return -1;
	else {
		for(int i = a.len - 1; i >= 0; i--) {
			if(a.d[i] > b.d[i]) return 1;
			else if(a.d[i] < b.d[i]) return -1;
		}
		return 0;
	}
}

5.6.2 大整数的四则运算

1. 高精度加法

bign add(bign a, bign b) {
	bign c;
	int carry = 0; //进位
	for(int i = 0; i < a.len || i < b.len; i++) {
		int temp = a.d[i] + b.d[i] + carry;
		c.d[c.len++] = temp % 10;
		carry = temp / 10;
	}
	if(carry != 0) {
		c.d[c.len++] = carry;
	}
	return c;
}
  • 这样写法的条件时两个对象都是非负整数。如果有一方是负的,可以在转换到数组这一步时去掉其负号,然后采用高精度减法;如果两个都是负的,就都去掉负号后用高精度加法,最后再把负号加回去即可。

2. 高精度减法

bign sub(bign a, bign b) {
	bign c;
	for(int i = 0; i < a.len || i < b.len; i++) {
		if(a.d[i] < b.d[i]) {  //如果不够减
			a.d[i+1]--;  //向高位借位
			a.d[i] += 10;  //当前位加10
		}
		c.d[c.len++] = a.d[i] - b.d[i];
	}
	while(c.len - 1 >= 1 && c.d[c.len - 1] == 0) {  
		c.len--;  //去除高位的0,同时至少保留一位最低位
	}
}
  • 使用sub函数前要比较两个数的大小,如果被减数小于减数,需要交换两个变量,然后输出负号,再使用sub函数

3. 高精度与低精度乘法

bign multi(bign a, int b) {
	bign c;
	int carry = 0;  //进位
	for(int i = 0; i < a.len; i++) {
		int temp = a.d[i] * b + carry;
		c.d[c.len++] = temp % 10;
		carry /= 10;
	}
	while(carry != 0) { //和加法不一样,乘法的进位可能不止一位,因此用while
		c.d[c.len++] = carry % 10;
		carry /= 10;
	}
	return c;
}
  • 如果 a 和 b 中存在负数,需要先记录下其负号,然后取他们的绝对值带入函数。

4. 高精度与低精度除法

bign divide(bign a, bign b, int &r) {  //r为余数
	bign c;
	c.len = a.len;  //被除数的每一位和商的每一位是一一对应的,因此先令长度相等
	for(int i = a.len - 1; i >= 0; i--) {  //从高位开始
		r = r * 10 + a.d[i];  //和上一位遗留的余数结合
		if(r < b) c.d[i] = 0;  //不够除,该位为0
		else {  //够除
			c.d[i] = r / b;  //商
			r = r % b;  //新的余数
		}
	}
	while(c.len - 1 >= 1 && c.d[c.len - 1] == 0) {  
		c.len--;  //去除高位的0,同时至少保留一位最低位
	}
	return c;
}

5.7 扩展欧几里得算法

5.8 组合数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值