数学问题

简单数学

数字黑洞

PAT 1019 数字黑洞:

https://pintia.cn/problem-sets/994805260223102976/problems/994805302786899968

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;

int nums_array[4];
//void to_array(int n){
//	// 四位数字转数组
//	nums_array[0] = n/1000;
//	nums_array[1] = n/100%10;
//	nums_array[2] = n/10%10;
//	nums_array[3] = n%10;
//}
void to_array(int n){
	for(int i=0;i!=4;++i){
		nums_array[i] = n%10;
		n/=10;
	}
} 
//int to_number(){
//	int number = nums_array[0] * 1000 + nums_array[1]*100 + nums_array[2]*10 +nums_array[3];
//	return number;
//}

int to_number(){
	int sum = 0;
	for(int i=0;i!=4;++i){
		sum = sum*10 + nums_array[i];
	}
	return sum;
}
bool cmp(int a,int b){
	return a > b;
}
int main()
{
	int n;
	cin >> n;
	
	while(1){
		to_array(n);
		sort(nums_array,nums_array+4,cmp);
		int max = to_number();
		sort(nums_array,nums_array+4);
		int min = to_number();
		n = max - min;
		// 如果不满四位数要注意高位补0 
		printf("%04d - %04d = %04d\n",max,min,n);
		if(n == 6174 || n == 0){
			break;
		}
	}                        
	return 0;
}
  • 数字转数组,数组再转数字
  • 排序
  • 输出格式化问题:不满四位数字高位补零

百鸡问题

http://tk.hustoj.com/problem.php?id=1957

for(int i=0;i<=100;++i){
    for(int j=0;j<=100;++j){
        for(int k=0;k<=100;++k){
            if(5*i + 3*j + (1.0/3.0) *k <= n){
                if(i + j + k == 100){
                    printf("x=%d,y=%d,z=%d\n",i,j,k);
                    //cout << "x=" << i<< ",y=" << j << ",z=" << k << endl;	
                }
            }
        }
    }
}
  • cout输出太影响性能
for(int i=0;i<=n/5;++i){
    // 一只五块钱,最多买 n/5 只
    for(int j=0;j<=(n-5*i)/3;++j){
        // 买了i只大鸡,还能买 (n-5*i)/3只小鸡
        for(int z=0;z<=(n-5*i-3*j)/(1.0/3.0);++z){
            if(i + j +z == 100){
                printf("x=%d,y=%d,z=%d\n",i,j,z);
            }
        } 
    } 
}
  • 不必从0到100计算
  • 有多少钱买多少鸡,买了多少大鸡就减去买了大鸡的钱,再去买小鸡

字符串和数字的转换

在有些题目中将数字转换为字符串而不是直接用数学规律解决会更为简单

sprintf

int sprintf(char *str, const char *format, ...)

  • 将数字变量转换为字符串
  • 连接多个字符串
char str[10];
int data = 1234;
sprintf(str,"%d",data);
cout << str << endl;  // 1234

sscanf

从字符串中读取数字

int data2 = 789;
sprintf(str,"%d%d",data,data2);
cout << str << endl;

// 从字符串中读取数字
scanf("%s",&str);  // 1234abc
int number;
sscanf(str,"%d",&number);
cout << number << endl; // 1234

char数组的长度

char a[100];
sizeof(a)/sizeof(a[0]); // 100
strlen(a); // 实际存放元素的个数

守形数

守形数是这样一种整数,它的平方的低位部分等于它本身。比如25的平方是625,低位部分是25,因此25是一个守形数。编一个程序,判断N是否为守形数。

输入包括1个整数N,2<=N<100。

可能有多组测试数据,对于每组数据,输出"Yes!”表示N是守形数。输出"No!”表示N不是守形数。

样例输入

6
11

样例输出

Yes!
No!
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;

int main()
{
	int n;
	while(~scanf("%d",&n)){
		char str[10],sstr[10];
		sprintf(str,"%d",n);
        // 平方转字符数组
		sprintf(sstr,"%d",n*n);
		
		int flag = 1;
		int length = strlen(sstr);
		// str 等于 sstr的低位
		int j = strlen(sstr) - 1; 
		for(int i=strlen(str) - 1;i>=0;--i){
			if(str[i] != sstr[j--]){
				flag = 0;
			}
		}
		if(flag == 0){
			cout << "No!" << endl;
		}else{
			cout << "Yes!" << endl;
		}
	}
	return 0;
}

从后往前遍历,用str的每一位检查是否与sstr的低位相等,若不是全部相等说明非守形数

最大公约数和最小公倍数

递归的两个核心:递归式和递归边界

函数递归的过程就是不断减小问题规模的过程,直到问题规模小到可以求出边界解,就在返回的过程中求出这个解。

最大公约数

核心算法: gcd(a,b) = gcd(b,a%b) 或写作:gcd(a,b) = gcd(b,a mod b) ,证明略

这个算法可以很快地减小最大公约数的求解规模,且不必在意a、b两个数谁大谁小

比如:

又因为 gcd(a,0) = a 0和任何数的最大公约数都是这个数本身

这就是一个天然的递归边界

容易用递归实现:

int gcd(int a, int b){
	if( b==0 ){
		return a;
	}else{
		return gcd(b,a%b);
	}
}

最小公倍数

有了最大公约数自然而然就会想到最小公倍数为 两数之积再除以最大公约数 a*b/gcdNum

但是 a*b容易出现溢出问题,又a/gcdNum 一定为一整数,所以常写为

a/gcdNum * b

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)){
		// 最大公约数: 
		int gcdNum = gcd(a,b);
		// 最小公倍数
		int lcmNum = a / gcdNum * b;
		cout << lcmNum << endl;
	}
	
	return 0;
}

求多个数的最小公倍数:多个数的最小公倍数是其中任意两个数的最小公倍数

例如要求4,6,12的最小公倍数:先求4和6的最小公倍数,用4和6的最小公倍数再去和12求最小公倍数

多个数的最小公倍数和最大公约数利用的原理都是类似的

codeup 2136 Least Common Multiple

题目描述

The least common multiple (LCM) of a set of positive integers is the smallest positive integer which is divisible by all the numbers in the set. For example, the LCM of 5, 7 and 15 is 105.

输入

Input will consist of multiple problem instances. The first line of the input will contain a single integer indicating the number of problem instances. Each instance will consist of a single line of the form m n1 n2 n3 ... nm where m is the number of integers in the set and n1 ... nm are the integers. All integers will be positive and lie within the range of a 32-bit integer.

输出

For each problem instance, output a single line containing the corresponding LCM. All results will lie in the range of a 32-bit integer.

样例输入

2
2 3 5
3 4 6 12

样例输出

15
12
#include <iostream>
#include <stdio.h>
using namespace std;

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int gcd2136(int a,int b){
	if(b==0){
		return a;
	}else{
		return gcd2136(b,a%b);
	}
} 

int main(){
	int n;
	while(~scanf("%d",&n)){
		for(int i=0;i!=n;++i){
			int m;
			cin >> m;
			int first;
			cin >> first;
			for(int j=0;j!=m - 1;++j){
				int second;
				cin >> second;
				int d = gcd2136(first,second);
				first = first / d*second;
			}
			cout << first << endl;
		}
	}
	return 0;
}

分数

分数的存储,化简,四则运算,输出

存储

为避免在乘法的过程中发生溢出,常将分子分母定义为long long

struct Fraction{
	long long int down,up; // 分子和分母 
}; 

化简

进行四则运算后,输出前都应对分数进行化简再处理

  • 负分数:约定分子为负数,如果分母为负数,则将分子分母都变为原来的相反数
  • 分母为0:如果分母为0则令分子为1
  • 化简:分子分母同除以他们的最大公约数(无需化简则代表同时除1)
Fraction reduction(Fraction res){
	if(res.down < 0){
		// 分母为负数,则分子分母改变为原来的相反数 
		res.up = - res.up;
		res.down = - res.down;
	}
	if(res.down == 0){
		// 分母为0则约定分子为1 
		res.up = 1;
	}
	// 化简 
	int d = gcd(abs(res.down),abs(res.up));
	res.down = res.down / d;
	res.up = res.up /d;
	return res;
	
}

四则运算

Fraction add(Fraction f1,Fraction f2){
	Fraction res;
	// 分母 
	res.down = f1.down * f2.down; 
	// 分子
	res.up =  f1.up*f2.down + f1.down * f2.up;
	// 化简 
	return reduction(res);
}
Fraction minu(Fraction f1,Fraction f2){
	Fraction res;
	res.down = f1.down * f2.down; 
	res.up =  f1.up*f2.down - f1.down * f2.up;
	return reduction(res);
}
Fraction multi(Fraction f1,Fraction f2){
	Fraction res;
	res.down = f1.down * f2.down;
	res.up = f1.up * f2.up;
	return reduction(res);
}
Fraction divide(Fraction f1,Fraction f2){
	Fraction res;
	res.down = f1.down * f2.up;
	res.up = f1.up * f2.down;
	return reduction(res);
}

输出

化简后的三种情况

  • 整数:分母为1,直接输出分子
  • 假分数:分子大于分母的情况,对分子取绝对值,输出为带分数(8/3 = 2 2/3)
  • 真分数:按照分数的格式输出
void printResult(Fraction res){
	// 输出的三种情况:真分数 假分数->带分数 整数 
 	 reduction(res);
	 if(res.up > res.down){
         // 带分数
	 	printf("%d %d/%d",res.up/res.down, abs(res.up)%res.down,res.down);
	 }else if(res.down == 1){
	 	// 化简后分母为1 : 整数 
	 	printf("%d",res.up);
	 }else{
	 	printf("%d/%d",res.up,res.down);
	 }
}

素数

素数:除了1和本身外不能被其他数所整除的数字为素数

存在任意数a(1 < a < n) 使得 n(n > 1) % a != 0,则a为素数

判断素数

定义判断

数n不能被任意一个a(1<a<n)整除

bool isPrime(int n){
	if(n<=1){
		return false;
	}else{
		for(int i=2;i!=n;++i){
			if(n%i==0){
				return false;
			}
		}
		return true;
	}
}

优化

假设n(n>1)为合数,则表示存在因数a、b(1<a,b<n),使得 n = a * b成立

又 n = 根号n * 根号n;

则有 a、b要么都等于根号n

要么一个大于根号n,一个小于根号n

由此:只需要遍历 1 到 根号n,判断n是否能被1到根号n内的数整除,便可以得出n是否为素数

bool isPrime(int n){
	if(n<=1){
		return false;
	}else{
		for(int i=2;i<=sqrt(n);++i){
			if(n%i==0){
				return false;
			}
		}
		return true;
	}
}

输出素数表

将某一范围的正整数从小到大进行遍历,若一个数是素数就把这个数和它的倍数全部筛掉,当从小到大地到达某个数时,如果它没有被筛掉那么它一定为素数(因为如果为合数就会被它的某个素因子作为倍数筛掉)。

只需要知道2为素数就可以推出的素数表

const int maxn = 101;
int primes[maxn],pNums = 0; // 记录所有的素数 和 素数的个数 
bool p[maxn] = {false}; // 记录某个数是否为素数
for(int i=2;i!=maxn;++i){
    if(p[i] == false){   // 2 为素数 
        primes[pNums++] = i;
        for(int j=2*i;j<=maxn;j+=i){
            p[j] = true;      
        }
    }
}
for(int i=0;i!=pNums;++i){
    cout << primes[i] << endl;
} 

质因子分解

质因子分解是指将一个数分解为一个或多个质数(素数)的乘积的形式。1不是素数,1也没有质因子。

比如:8 = 2 * 2 * 2

12 = 2 * 2 * 3

质因子分解中需要记录质因子和质因子的个数,所以常用结构体

struct factor{
  int x;
  int nums;
}fac[10];

对于不超过int范围的数,只需要将fac数组开到10就可以了。2*3*5*7*11*13*17*19已经超出了int范围。

对数n的质因子分解可以概括为两种情况:

  • n的所有质因子都小于等于sqrt(n)
  • n存在一个大于sqrt(n)的质因子,其余皆小于sqrt(n)

质因子分解的基本思路:

  • 先打印素数表

    void getPrimes(){
    	for(int i=2;i!= maxn;++i){
    		if(p[i] == false){
    			primes[pNums++] = i;
    			for (int j = i + i; j <= maxn; j += i) {
    				p[j] = true;
    			}
    		}
    	}
    }
    
  • 枚举1-sqrt(n)范围内所有的质因子p,判断是p是否为n的因子

    • 如果是,则令fac数组增加因子p,并初始化其个数为0,让n不断除以p,每次都令p的个数加1,直到p不为n的因子
    • 不是则直接跳过
    for(int i=0;i!=pNums && primes[i] <= sqr;++i){
        // primes[i] <= sqr 例如 9 = 3 * 3 
        if(n % primes[i] == 0){
            fac[pos].x = primes[i];
            fac[pos].cnt = 0;
            while(n%primes[i]==0){
                fac[pos].cnt++;
                n /= primes[i];
            }
            pos++;
        }
        if(n == 1){
            break;
        }
    }
    
  • 以上步骤结束后,n仍然大于1,说明n存在一个大于sqrt(n)的质因子,此时将该数加入fac数组,令其个数为1

    if(n!=1){
        fac[pos].x = n;
        fac[pos++].cnt = 1;
    }
    

Reference

胡凡, 曾磊. 算法笔记[M]. 机械工业出版社, 2016.

posted @ 2021-02-12 10:08  巴啦啦胖魔仙  阅读(186)  评论(0)    收藏  举报