算法笔记--简单数学1
数字黑洞
题目
给定任一个各位数字不完全相同的 4 位正整数,如果我们先把 4 个数字按非递增排序,再按非递减排序,然后用第 1 个数字减第 2 个数字,将得到一个新的数字。一直重复这样做,我们很快会停在有“数字黑洞”之称的 6174,这个神奇的数字也叫 Kaprekar 常数。
例如,我们从6767开始,将得到
7766 - 6677 = 1089
9810 - 0189 = 9621
9621 - 1269 = 8352
8532 - 2358 = 6174
7641 - 1467 = 6174
... ...
现给定任意 4 位正整数,请编写程序演示到达黑洞的过程。
输入与输出
输出格式:
如果 N 的 4 位数字全相等,则在一行内输出 N - N = 0000;否则将计算的每一步在一行内输出,直到 6174 作为差出现,输出格式见样例。注意每个数字按 4 位数格式输出。
输入样例 1:
6767
输出样例 1:
7766 - 6677 = 1089
9810 - 0189 = 9621
9621 - 1269 = 8352
8532 - 2358 = 6174
输入样例 2:
2222
输出样例 2:
2222 - 2222 = 0000
解题思路
思路
步骤1:
写两个函数:int型整数转换成int型数组的to_array函数(即把每一位都当成数组的一个元素)、int型数组转换成int型整数的to_number函数。
步骤2:建立一个while循环,对每一层循环:
用to_array函数将n转换为数组并递增排序,再用to_number函数将递增排序完的数组转换为整数MIN.
将数组递减排序,再用to_number数将递减排序完的数组转换为整数MAX
令 n = MAX - MIN为下一个数,并输出当前层的信息。
如果得到的n为0或6174,退出循环。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
bool cmp(int a, int b){
return a > b; // 从大到小排序
}
void to_array(int number, int myArray[]){ // 转换为数组
for(int i = 0; i < 4; i++){
myArray[i] = number % 10; // 利用求余
number /= 10;
}
}
// 数组转换数字技巧
//如 1234 = ((((0 * 10 + 1) * 10 + 2) * 10 + 3) * 10 + 4)
int to_number(int myArray[]){
int number = 0;
for(int i = 0; i < 4; i++){
number = number * 10 + myArray[i];
}
return number;
}
int main(){
int number, MIN, MAX;
int myArray[5];
scanf("%d", &number);
while(1){
to_array(number, myArray);
sort(myArray, myArray + 4);
MIN = to_number(myArray);
sort(myArray, myArray + 4, cmp);
MAX = to_number(myArray);
number = MAX - MIN;
printf("%04d - %04d = %04d\n", MAX, MIN, number);
if(number == 0 || number == 6174)break;
}
return 0;
}
最大公约数与最小公倍数
最大公约数
利用欧几里得算法(辗转相除法)j计算最大公约数
169 = 48 * 3 + 25 —— (169 , 48) = (48 , 25)
48 = 25 * 1 + 13 ——(48 , 25) = (25 , 13)
25 = 13 * 1 + 12 ——(25 , 13) = (13 , 12)
13 = 12 * 1 + 1 ——(13 , 12) = (12 , 1)
12 = 1 * 12 + 0 ——(12 , 1 ) = (1 , 0) 此时 b = 0
故最大公约数为 1
递归的关键步骤:
- 🅰递归式:
gcd(a, b) = gcd(b, a % b) - 🅱递归边界:
gcd(a, 0) = a
int gcd(int a, int b){
if(b == 0) return a;
else return gcd(b, a % b);
}
题目: [codeup 1818]最大公约数
题目描述:
输入两个正整数,求其最大公约数
输入:
测试数据有多组,每组输入两个正整数
输出:
对于每组输入,请输出其最大公约数
样例输入:
49 14
样例输出:
7
代码
#include<cstdio>
int gcd(int a, int b){
if(b == 0) return a;
else return gcd(b, a % b);
}
int main(){
int a, b;
while(scanf("%d%d", &a, &b) != EOF){
printf("%d", gcd(a, b));
}
return 0;
}
最小公倍数
一般用lcm(a,b)来表示a和b的最小公倍数
最小公倍数的求解在最大公约数的基础上进行,当得到gcd(a, b) = d,可以马上得到a,b的最小公倍数是ab/d,但是ab计算可能会溢出,因此应写为a/bd
int lcm(int a, int b){
int d = gcd(a, b);
return a / d * b;
}
分数的四则运算
给定两个分数的分子和分母,求他们加减乘除的结果
分数的表示和化简
分数的表示
对于一个分数来说,最简洁的写法就是写成假分数的形式,无论分子分母大或者小,都保留其原数
struct Fraction{ // 分数
int up, down; // 分子 分母
}
- 1️⃣使用
down为非负数。如果分数为负,那么令分子up为负即可 - 2️⃣如果该分数恰好为0,则令分子为0,分母为1
- 3️⃣分子和分母没有除了1以外的公约数
分数的化简
- 🟡如果分母down为负数,那么令分子up和分母down都变为相反数
- 🟢如果分了up为0,那么令分母down为1
- 🔵约分:求出分子绝对值与分母绝对值的最大公约数d,然后令分子分母同时除以d
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;
}
分数的四则运算
- 分数的加法
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); // 结果注意化简
}
- 分数的减法
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);
}
- 分数的乘法
Fraction add(Fraction f1, Fraction f2){
Fraction result;
result.up = f1.up * f2.up;
result.down = f1.down * f2.down;
return reduction(result);
}
- 分数的除法
Fraction add(Fraction f1, Fraction f2){
Fraction result;
result.up = f1.up * f2.down;
result.down = f1.down * f2.up;
return reduction(result);
}
分数的输出
- 输出分数前,需要先对其进行化简。
- 如果分数r的分母down为1,说明该分数是整数,一般来说题目会要求直接输出分
子,而省略分母的输出。 - 如果分数r的分子up的绝对值大于分母down,说明该分数是假分数,此时应按带分数的形式输出,即整数部分为
r.up/r.down,分子部分为abs(r.up)%r.down,分母部分为r.down - 以上均不满足时说明分数r是真分数,按原样输出即可。
void showResult(Fraction r){
r = reduction(r);
if(r.down == 1) printf("%d", 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型来存储。
素数
素数又称为质数,是除了1和本身之外,不能被其他数整数的一类数
1既不是质数,也不是合数
素数的判断
- 如果利用N能否被
2, 3, ..., N - 1整除,其时间复杂度为O(n) - 可以利用N能否被
2, 3, ..., (int)(sqrt(N))的一个整数整除,其时间复杂度为O(sqrt(n))
#include<math.h>
#include<stdio.h>
bool isPrime(int n){
if(n <= 1) return false;
int sqr = (int)sqrt(1.0 * n); // sqrt的参数为浮点数
for(int i = 2; i < sqr; i++){
if(n % i == 0) return false;
}
return true;
}
素数表的获取
- 🅰枚举法
要打印1到n的素数,即从1到n进行枚举,判断每个数是否是素数,如果是则加入素数表。
枚举的时间复杂度为O(n),判断素数的时间复杂度为O(sqrt(n)),所以,总的时间复杂度为O(n*sqrt(n)),这个复杂度对n不超过\(10^5\)的大小是没有问题的
const int maxn = 101;
int prime[maxn], pNum = 0;
bool p[maxn] = {0};
void FindPrime(){
for(int i = 1; i < maxn; i++){
if(isPrime(i) == true){
prime[pNum++] = i;
p[i] = true;
}
}
}
求解100以内的素数(枚举法)
#include<stdio.h>
#include<math.h>
const int MAXN = 101;
int pNum = 0;
int prime[MAXN];
bool p[MAXN] = {false};
bool isPrime(int n){
if(n <= 1) return false;
int sqr = (int)sqrt(1.0 * n);
for(int i = 2; i <= sqr; i++){
if(n % i == 0) return false;
}
return true;
}
void FindPrime(){
for(int i = 1; i < MAXN; i++){
if(isPrime(i) == true){
prime[pNum++] = i;
p[i] = true;
}
}
}
int main(){
FindPrime();
for(int i = 0; i < pNum; i++)
printf("%d\t", prime[i]);
return 0;
}
🅱筛选法
例子:筛选出1~15中的所有素数

代码
const int MAXN = 101;
int prime[maxn], pNum = 0;
bool p[MAXN] = {0};
void FindPrime(){
for(int i = 2; i < MAXN; i++){
if(p[i] == false){ // 如果是素数
prime[pNum++] = i;
for(int j = i + i; j < MAXN; j += i){ //筛去所有i的倍数
p[j] = true; // 循环条件不能写成j <= MAXN
}
}
}
}
求解100以内的素数(筛选法)
#include<stdio.h>
const int MAXN = 101;
int prime[MAXN], pNum = 0;
bool p[MAXN] = {0};
void FindPrime(){
for(int i = 2; i < MAXN; i++){
if(p[i] == false){ // 如果是素数
prime[pNum++] = i;
for(int j = i + i; j < MAXN; j += i){ //筛去所有i的倍数
p[j] = true; // 循环条件不能写成j <= MAXN
}
}
}
}
int main(){
FindPrime();
for(int i = 0; i < pNum; i++){
printf("%d\t", prime[i]);
}
}
[PAT B1013] 数素数
令Pi为第i个素数,在一行中输入正整数M和N(M<=N<=10000),以空格分隔,输出PM到PN之间的所有素数,每10个数字占1行,其间以空格分隔,行末不得有多余空格。
输入样例:
5 27
输出样例:
11 13 17 19 23 29 31 37 41 43
47 53 59 61 67 71 73 79 83 89
97 101 103
#include<cstdio>
const int MAXN = 10000001; // 不知道第10000个素数大小,所以MAXN设大点
int pNum = 0, prime[MAXN];
bool p[MAXN] = {0}; // false表示素数
void FindPrime(int n){
for(int i = 2; i < MAXN; i++){
if(p[i] == false){
prime[pNum++] = i;
if(pNum >= n)break;
for(int j = i + i; j < MAXN; j += i){
p[j] = true;
}
}
}
}
int main(){
int m, n, count = 0;
scanf("%d%d", &m, &n);
FindPrime(n);
for(int i = m; i <= n; i++){
printf("%d", prime[i - 1]); // 下标从0开始
count ++;
if(count % 10 != 0 && i < n)printf(" ");
else printf("\n");
}
return 0;
}
注意
⭕1不是素数。
⭕素数表长至少要比n大1
⭕在FindPrime()函数中要特别留意i<MAXN不能写成i≤MAXN
⭕main函数中要记得调用FindPrime(),不然不会出结果
质因子分解
质因子的分解就是指将一个正整数n写成一个或者多个质数的乘积的形式,如180 = 2*2*3*3*5,或者写成\(180 = 2^2 * 3^2 * 5^1\)
注意:由于1本身不是素数,因此它没有质因子,如果有些题目要求对1进行处理,那么视题目条件而定来进行特判处理
定义结构体factor来存放质因子及其个数
struct factor{
int x, cnt;
}fac[10];
fac[]数组存放的就是给定的正整数n的所有质因子,如180:
fac[0].x = 2;
fac[0].cnt = 2;
fac[1].x = 3;
fac[1].cnt = 2;
fac[2].x = 5;
fac[2].cnt = 1;
因为2*3*5*7*11*13*17*19*23*29已经超过int范围,所以对一个int型的数来说,fac数组的大小只要开到10就够够的了
对一个整数n来说,如果它存在[2,n]范围内的质因了,要么这些质因子全部小于等于sqrt(n),要么只存在一个大于sqrt(n)的质因了,而其余质因子全部小于等于sqrt(n)这就给进行质因了分
解提供了一个很好的思路:
-
🅰枚举
1~sqrt(n)范围内的所有质因子p,判断p是否是n的因子如果p是n的因子,则执行如下:
if(n % prime[i] == 0){ // 如果prime[i]是n的因子
fac[num].x = prime[i]; // 记录该因子
fac[num].cnt = 0; // 初始化其个数为0
while(n % prime[i] == 0){ // 计算出质因子prime[i]的个数
fac[num].cnt++; // 只要p还是n的因子,就让n不断除以p,每次操作令p的个数加1
n /= prime[i]; // 直到p不再是n的因子为止
}
num++;
}
如果p不是n的因子,就直接跳过
- 🅱如果在上面步骤结束后n仍然大于1,说明n有且仅有一个大于
sqrt(n)的质因子(有可能是n本身),这时需要把这个质因了加入fac数组,并令其个数为1
if(n != 1){ //如果无法被根号n以内的质因子除尽
fac[num].x = n; // 那么一定有一个大于根号n的质因子
fac[num++].cnt = 1;
}
至此,fac数组中存放的就是质因子分解的结果,时间复杂度为O(sqrt(n))
例题Prime Factors
题目描述:
Given any positive integer N, you are supposed to find all of its prime factors, and write them in the format N = p1^k1 * p2^k2 *…*pm^km.
输入样例:
97532468
输出样例:
97532468 = 2^2 * 11 * 17 * 101 * 1291
思路
利用上面学习的内容,要在前面先把素数表打印出来,然后进行质因子分解操作
代码
#include<cstdio>
#include<cmath>
const int MAXN = 100005;
int pNum = 0, prime[MAXN];
int p[MAXN] = {0};
bool isPrime(int n){ // 判断是否是素数
if(n == 1) return false;
int sqr = (int)sqrt(1.0 * n);
for(int i = 2; i <= sqr; i++){
if(n % i == 0) return false;
}
return true;
}
void FindPrime(){ // 构建素数表
for(int i = 1; i < MAXN; i++){
if(isPrime(i) == true){
prime[pNum++] = i;
for(int j = i + i; j < MAXN; j += i){
p[j] = true;
}
}
}
}
struct factor{ // 质因子的结构体
int x, cnt; // x表示质数,cnt表示个数
}fac[10];
int main(){
FindPrime();
int n, num = 0;
scanf("%d", &n);
if(n == 1)printf("1 = 1");
else{
printf("%d = ", n);
int sqr = (int)sqrt(1.0 * n);
// 枚举根号n以内的质因子
for(int i = 0; i < pNum && prime[i] <= sqr; i++){
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++;
}
if(n == 1)break;
}
if(n != 1){
fac[num].x = n;
fac[num++].cnt = 1;
}
for(int i = 0; i < num; i++){
if(i > 0)printf("*");
printf("%d", fac[i].x);
if(fac[i].cnt > 1){
printf("^%d", fac[i].cnt);
}
}
}
return 0;
}
大整数的运算
大整数又称为高精度整数,其含义是用基本数据类型无法存储其精度的整数
大整数的存储
如23513存储在数组中,则
d[0] = 3;
d[1] = 1;
d[2] = 5;
d[3] = 3;
d[4] = 2;
🉐整数的高位存储在数组的高位,整数的低位存储在数组的低位
但是,整数按照字符串%s读入时,实际上是逆位存储的,即
str[0] = '2';
str[1] = '3';
str[2] = '5';
str[3] = '1';
str[4] = '3';
🉐在读入后需要在另存至d[]数组时反转一下
定义大整数结构体
struct bign{
int d[1000];
int len; // 记录长度
};
在定义结构体变量后,需要马上初始化结构体,所以有:
struct bign{
int d[1000];
int len;
bign(){
memset(d, 0, sizeof(d));
len = 0;
}
};
在输入大整数时,先用字符串读入,然后再把字符串另存为bign结构体
bign change(char str[]){
bign a;
a.len = strlen(str);
for(int i = 0; i < a.len; i++){
a.d[i] = str[a.len - i - 1] - '0'; // 逆向赋值
}
return a;
}
比较两个bign大小
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; // 两数相等
}
}
大整数的四则运算
- 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){ // 如果最后进位不是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]){
b.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,同时至少保留一位最低位
}
return c;
}
最后需要指出,使用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 = temp / 10; // 高位部分作为新的进位
}
while(carry != 0){ // 进位可能不止一位,所以用循环
c.d[c.len++] = carry % 10;
carry /= 10;
}
return c;
}
如果a和b中存在负数,需要先记录下负号,然后取它们的绝对值代入函数。
-
4️⃣高精度与低精度除法
上一步的余数乘以10加上该步的位,得到该步临时的被除数,将其与除数比较:如果不够除,则该位的商为0; 如果够除,则商即为对应的商,余数即为对应的余数。最后一步要注意减法后高位可能有多余的0,要忽视它们,但也要保证结果至少有一位数。
bign divide(bign a, int 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[len - 1] == 0){
c.len--; // 去除高位的0,同时至少保留一位最低位
}
return c;
}
由于引用就是对原变量进行修改的,所以r的值就是最终的余数
Write by Gqq

浙公网安备 33010602011771号