6 数学问题
机试中的数学问题,不涉及深奥的算法和数据结构,只与数理逻辑相关,这里包括了:
进制转换、最大公约数、最小公倍数、质数、分解质因数、快速幂、矩阵与矩阵快速幂、高精度整数
1 进制转换
有3种,10进制转n进制、m进制转10进制、m进制转n进制,例题和习题的顺序也是这样排列的。
例题6.1 二进制数(北邮复试上机)
题目
这道题为10进制转2进制
题解
#include <vector>
#include <cstdio>
using namespace std;
int main(){
unsigned int num;
while(scanf("%d",&num)!=EOF){
vector<int> binary; //由于是“变长数组”,用vector
while(num!=0){
binary.push_back(num%2); //%2不会产生0,不会将0 push到binary中
num /=2;
}
//由于binary中低位在前,所以要将binary逆序输出才有正常的二进制数
for(int i = binary.size()-1;i>=0;i--)
{
printf("%d",binary[i]);
}
printf("\n");
}
}
笔记
- 二进制位数未知,所以用vector当变长数组用
- num!=0,所以不用担心num%2会产生0,不会将0放入binary中,也就不会有二进制串的前导0
- 十进制转换二进制过程中,先产生的是原二进制中的低位,所以结果要逆序输出
习题6.1 八进制(华中科技大学复试上机)
题目
这道题将上一道题的方法扩展为10进制转n进制
题解
#include <cstdio>
#include <string>
#include <vector>
using namespace std;
//十进制转化为n进制
vector<int> ConvertT2N(int number, int n){
vector<int> answer;
if(number == 0){
answer.push_back(0); //单独处理0的情况
}
else{
while(number != 0){
//转换为n进制,所以对n做计算
answer.push_back(number%n);
number /= n;
}
}
return answer;
}
int main(){
int number;
while(scanf("%d",&number)!=EOF){
vector<int> answer = ConvertT2N(number,8); //本题为转化为8进制
//逆序输出
for(int i = answer.size()-1;i>=0;i--){
printf("%d",answer[i]);
}
printf("\n");
}
}
笔记
- 可以把10进制转换为2进制,单独写成一个函数,且可以泛化为求n进制,但是这里的限制是n必须小于10
- 单独处理number为0的情况
习题6.1扩展 十进制转化为16进制
上一道题虽然将10进制转2进制扩展为转n进制,但不允许n大于10。
本题通过int与char的转换,实现10进制转换为16进制
题解
#include <cstdio>
#include <string>
#include <vector>
using namespace std;
//当目标进制可能大于10时,选择统一用字符
char Int2Char(int target){
if(target<10){
return target + '0'; //如果目标进制小于10,则返回数字对应的字符
}
else{
return target - 10 + 'A'; //如果目标进制大于10,则返回对应大写字母
}
}
//十进制转化为n进制,这里n可能大于10
vector<char> ConvertT2N(int number, int n){
vector<char> answer;
if(number == 0){
answer.push_back('0'); //统一用字符
}
else{
while(number != 0){
//转换为n进制,n可能大于10时,只有计算时用的int,其他都用char
answer.push_back(Int2Char(number%n)); //统一用字符
number /= n;
}
}
return answer;
}
int main(){
int number;
while(scanf("%d",&number)!=EOF){
vector<char> answer = ConvertT2N(number,16); //上一题为转化为8进制
//逆序输出
for(int i = answer.size()-1;i>=0;i--){
printf("%c",answer[i]); //当目标进制大于10,统一用字符输出
}
printf("\n");
}
}
笔记
- 10进制转换为n进制,n可能大于10时,统一用字符vector保存数位,并用字符%c输出
- Int2Char函数的写法
习题6.3 进制转换(北京大学复试上机)
题目
接下来是m进制转换为10进制,本题为16进制转换为10进制
题目地址
题解
#include <cstdio>
#include <iostream>
#include <string>
using namespace std;
//n进制转换为10进制,输入的是字符串,所以想拿来计算前要转换成int
int Char2Int(char target){
if('0' <= target && target <= '9'){
return target - '0'; //当原字符为数字的字符时,加'0'可得到数字本身
}
else {
return target - 'A' + 10; //当原字符不是数字,即十六进制10-15的数
}
}
//m进制转换为10进制
int ConvertM2T(string str,int n){
int number = 0;
for(int i = 0;i<str.size();++i){
//先让高位的str[0]乘基数,顺序没错
number *= n;
number += Char2Int(str[i]);
}
return number;
}
int main(){
string str;
while(cin>>str){
str = str.substr(2); //排除掉16进制前面的标识符
int number = ConvertM2T(str, 16);
printf("%d\n",number);
}
}
笔记
- 16进制转换为10进制,输入的是字符串,所以要有一个函数用来char转int,注意ascii的计算
- 10进制转换为n进制是number先%n后/n,先得到低位再得到高位;而n进制转换为10进制是先得到高位n再加上低位n
- 输入字符串时若有多余,可以substr求子串排除掉多余的字符
例题6.4 进制转换2(清华大学复试上机)
题目
本题为m进制转换为n进制,可以先从m进制转换为10进制,再从10进制转换为n进制
题目地址
题解
#include <cstdio>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int Char2Int(char from){
if('0' <= from && from <= '9'){
return from - '0';//字符-'0'后为对应的数字
}
else{
return from - 'A' + 10; //输入时为大写字母,所以仍用'A'
}
}
char Int2Char(long long from){
if(from < 10){
return from + '0';
}
else{
return from - 10 + 'a'; //这里注意题目要求为输出时用小写字母
}
}
//m进制转换为10进制
long long ConvertM2T(string mstr,int m){
long long number = 0; //所有number的类型都用longlong,不然会报错
for(int i = 0;i<mstr.size();i++){
number *= m;
number += Char2Int(mstr[i]);
}
return number;
}
//10进制转换为n进制
vector<char> ConvertT2N(long long number,int n){
vector<char> answer; //这里要定义一个answer来返回vector数据
if(number == 0){
answer.push_back('0');
}
else{
while(number != 0){
answer.push_back(Int2Char(number%n));
number /= n;
}
}
return answer;
}
int main(){
int m,n;
while(scanf("%d%d",&m,&n)!=EOF){
string mstr;
cin>>mstr; //cin已经略过了上一行留下的换行符
//先m进制转10进制,再10进制转n进制,使用longlong是本题数值过大
long long number = ConvertM2T(mstr, m);
vector<char> answer = ConvertT2N(number,n);
for(int i = answer.size()-1;i>=0;i--){
printf("%c",answer[i]);
}
printf("\n");
}
}
笔记
- m进制-10进制-n进制,注意4个函数的写法,和数值类型
- 注意细节:题目要求的字母大小写、避免数值超范围用long long
2 最大公约数和最小公倍数
例题6.5 最大公约数(哈工大复试上机)
题目
题解
#include <cstdio>
using namespace std;
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\n",GCD(a,b));
}
return 0;
}
笔记
- 求最大公约数的公式,欧几里得算法/ 辗转相除法,涉及递归调用
例题6.6 最小公倍数
最小公倍数的求解建立在最大公约数的基础上,就是个数学公式,
LCM(a,b) = (a*b) / GCD(a,b)
3 质数
包括:基础,给一个数判断是否为质数;区间内判断哪些是质数,也就是质数筛法;
例题6.7 素数判定(哈工大复试上机)
题目
基础题,求素数
题目地址
题解
#include <iostream>
#include <cstdio>
#include <cmath> //要用求根号的函数
using namespace std;
bool Judge(int n){
if(n < 2){
return false; //0,1,负数都是非素数
}
int bound = sqrt(n);
for(int i = 2;i<=bound;i++){ //注意这里可以=
if(n%i == 0){
return false;
}
}
return true;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
if(Judge(n)){
printf("yes\n");
}
else{
printf("no\n");
}
}
}
笔记
- 添加头文件cmath库,使用sqrt求开根号
- 注意细节:0,1,负数都不是素数;判断的边界可以=bound=根号n
例题6.8 素数(北航复试上机)
题目
本题求第k个质数,用到了素数筛法,题目无地址
题解
#include <vector>
#include <cstdio>
using namespace std;
const int MAXN = 1e5+10;
vector<int> prime; //保存质数
bool isPrime[MAXN]; //标记数组
void Initial(){
fill(isPrime,isPrime+MAXN,true); //fill的使用
isPrime[0] = false;
isPrime[1] = false;
for(int i = 2;i<MAXN;++i){
if(!isPrime[i]){
continue; //如果当前已经判断过不是质数,则跳过
}
prime.push_back(i);
//健壮性(可不写)
if(i < MAXN / i){
continue; //如果i*i过大,则不做处理,且判别式等价变换
}
//质数筛法,可以直接从i*i开始,避免重复计算,j+=i
for(int j = i*i;j<MAXN;j+=i){
isPrime[j] = false;
}
}
}
int main(){
int k;
while(scanf("%d",&k)!=EOF){
printf("%d\n",prime[k-1]);
}
}
笔记
- 用一个可动态增加的向量vector来记录素数,用const int MAXN来记录最大范围
- 记住质数筛法的写法,每次改isPrime的长度即可
4 分解质因数
先是求质因数,再根据质因数和约数个数的公式可以求约数的个数
例题6.9 质因数的个数(清华大学复试上机)
题目
本题求质因数的个数,用到了质数筛法,也用到了不太好记的统计质因数的流程
题目地址
题解
#include <cstdio>
#include <vector>
using namespace std;
//sqrt(1e9)将浮点数赋值给常整数需要高版本c++,所以可以自己口算后直接赋值整数
const int MAXN = 4e4;
vector<int> prime; //保存质数
bool isPrime[MAXN]; //标记数组
void Initial(){
fill(isPrime,isPrime+MAXN,true); //fill的使用
isPrime[0] = false;
isPrime[1] = false;
for(int i = 2;i<MAXN;++i){
if(!isPrime[i]){
continue; //如果当前已经判断过不是质数,则跳过
}
prime.push_back(i); //执行这一句时i已经必是质数了
//健壮性
if(i < MAXN / i){
continue; //如果i*i过大,则不做处理,且判别式等价变换
}
//质数筛法,可以直接从i*i开始,避免重复计算
for(int j = i*i;j<MAXN;j+=i){
isPrime[j] = false;
}
}
}
int main(){
int answer = 0; //统计质因数个数,其中同一个素数有幂指数按多次算
int n;
Initial();
while(scanf("%d",&n)!=EOF){
for(int i = 0;i<prime.size() && prime[i]<n;i++){
int factor = prime[i];
//不断试除该素数,统计其幂指数
while(n%factor == 0){
n /= factor;
answer ++;
}
}
//遍历完所有小于MAXN的素数后,若n>1,则存在一个大于MAXN的因子且必为1次质因子
if(n>1){
answer++;
}
printf("%d\n",answer);
}
}
笔记
- 质数筛法的流程
- 记住主函数中统计质因数的流程,不断试除、遍历后可能再+1
- 注意细节:const int MAXN赋值浮点数不稳妥,口算后赋值整数;主函数for的循环条件,以及别少写i++
习题 6.7 约数的个数(清华大学复试上机)
有个公式,约数的个数 = 所有质因数的(幂指数+1)相乘,比如12的质因数2,2,3,则约数个数为(2+1)*(1+1)= 6个
题目
题解
#include <cstdio>
#include <vector>
using namespace std;
//sqrt(1e9)将浮点数赋值给常整数需要高版本c++,所以可以自己口算后直接赋值整数
const int MAXN = 4e4;
vector<int> prime; //保存质数
bool isPrime[MAXN]; //标记数组
void Initial() {
fill(isPrime, isPrime + MAXN, true); //fill的使用
isPrime[0] = false;
isPrime[1] = false;
for (int i = 2; i < MAXN; ++i) {
if (!isPrime[i]) {
continue; //如果当前已经判断过不是质数,则跳过
}
prime.push_back(i); //执行这一句时i已经必是质数了
//健壮性
if (i < MAXN / i) {
continue; //如果i*i过大,则不做处理,且判别式等价变换
}
//质数筛法,可以直接从i*i开始,避免重复计算
for (int j = i * i; j < MAXN; j += i) {
isPrime[j] = false;
}
}
}
int main() {
int n;
Initial();
while (scanf("%d", &n) != EOF) {
if (n == 0) {
break;
}
for (int j = 0; j < n; j++) { //每行有n个数据
//统计约数的个数 = 每个质因数的(幂指数+1)再相乘,且初始化为1
int answer =1;
int number;
scanf("%d", &number);
for (int i = 0; i < prime.size() && prime[i] < number; i++) {
int exponent = 0; //记录本次质因数的幂指数
int factor = prime[i];
//不断试除该素数,统计其幂指数
while (number % factor == 0) {
number /= factor;
exponent++;
}
//每统计完一个质因数的幂指数,就累乘到约数个数answer上
answer *= exponent + 1;
}
//遍历完所有小于MAXN的素数后,若n>1,则存在一个大于MAXN的因子且必为1次质因子
if (number > 1) {
//与上一道题区别是,这里直接把最后一个质因数的幂指数1+1,
//乘到约数个数answer上即可
answer *= 2;
}
printf("%d\n", answer);
}
}
}
笔记
- 与上一道例题的区别,需要根据题目修改输入输出格式,和对answer的初始化数值、初始化位置、和试除后的处理。
- 以前answer是初始化在主函数最开头,这道题多个输入,所以把answer初始化在每次输入后,且不是初始为0,而是1
- 以前answer是不断试除然后+1,并且最后再单独判断是否还有一个质因数;而这道题是用一个exponent代替原来的answer来记录每个质因数的幂指数
- 并且answer每次试除成功后answer *= exponent +1来记录约数的个数,最后如果还有1个质因数,则answer还要最后乘这个质因数的幂指数(1+1)
5 快速幂、矩阵、矩阵快速幂
快速幂的思想是:answer初始为1,将幂指数当二进制,这样就变成了原数“累乘所有二进制位的数值”,具体步骤记代码。
int answer = 1;
while(mi != 0){
if(mi % 2 == 1){
answer *= number;
//answer %= mod; //如果只需要求最后几位,可以在每次这个位置加上
}
mi /= 2;
number *= number; //底数不断平方,不管幂指数对应的二进制位当前是否为1
//number %= mod; //如果只需要求最后几位,可以每次在这里加上
}
return answer;
而矩阵求快速幂时的思想:套用快速幂的思想,不过answer初始为单位阵,较复杂,不过做题时现场能暴力写出来