机试指南练习总结第四章
机试指南练习总结第四章-%运算符
首先还是需要介绍一下%运算符,%运算符用来计算余数,同时余数的符号与被除数保持一致。同时,为了将负整数的余数统一在0~(除数-1)
之间,需要加上表达式r = (r + b) % b
%运算符有以下运算性质:
(a * b) % c = ((a % c) * (b % c) ) % c
(a + b) % c = ((a % c) + (b % c) ) % c
练习题1:还是A+B
题目描述
读入两个小于10000的正整数A和B,计算A+B。需要注意的是:如果A和B的末尾K(不超过8)位数字相同,请直接输出-1。
输入描述
测试输入包含若干测试用例,每个测试用例占一行,格式为"A B K",相邻两数字有一个空格间隔。当A和B同时为0时输入结束,相应的结果不要输出。
输出描述:
对每个测试用例输出1行,即A+B的值或者是-1。
示例1
输入:
1 2 1
11 21 1
108 8 2
36 64 3
0 0 1
输出:
3
-1
-1
100
解题思路
这题比较简单,直接利用%运算符就能解决。
#include <iostream>
using namespace std;
int main()
{
int a, b, k;
while(cin>>a>>b){
if(a == 0 && b == 0){
break;
}
cin>>k;
int c = a, d = b;
while(k > 0){
if(c % 10 != d % 10){
break;
}else{
c /= 10;
d /= 10;
k--;
}
}
cout<<((k == 0) ? -1 : (a + b))<<endl;
}
return 0;
}
练习题2:守形数
题目描述
守形数是这样一种整数,它的平方的低位部分等于它本身。 比如25的平方是625,低位部分是25,因此25是一个守形数。 编一个程序,判断N是否为守形数。
输入描述
输入包括1个整数N,2<=N<100。。
输出描述:
可能有多组测试数据,对于每组数据,
输出"Yes!”表示N是守形数。
输出"No!”表示N不是守形数。
示例1
输入:
25
4
输出:
Yes!
No!
解题思路
这题比较简单,直接利用%运算符就能解决,反复对n以及其的平方数取余,直到n<=0
即可解决问题,代码如下:
#include <iostream>
using namespace std;
int main()
{
int n;
while(cin>>n){
int m = n * n;
while(n > 0){
if(n % 10 != m % 10){
break;
}else{
n /= 10;
m /= 10;
}
}
cout<<((n == 0) ? "Yes!" : "No!")<<endl;
}
return 0;
}
机试指南练习总结第四章-数位拆解
例题4.1:特殊乘法
题目描述
写个算法,对2个小于1000000000的输入,求结果。 特殊乘法举例:123 * 45 = 14 +15 +24 +25 +34+35
输入描述
两个小于1000000000的数
输出描述:
输入可能有多组数据,对于每一组数据,输出Input中的两个数按照题目要求的方法进行运算后得到的结果。
示例1
输入:
123 45
输出:
54
解题思路
这题比较简单,直接使用%和/交替运算就能得到结果。代码如下:
#include <iostream>
#define MAX 10
using namespace std;
int main()
{
int a, b;
int num1[MAX], num2[MAX];
while(cin>>a>>b){
int res = 0;
int i, j;
for(i=0; a>0; i++){
num1[i] = a % 10;
a /= 10;
}
for(j=0; b>0; j++){
num2[j] = b % 10;
b /= 10;
}
int k, x;
for(k=0; k<i; k++){
for(x=0; x<j; x++){
res += num1[k] * num2[x];
}
}
cout<<res<<endl;
}
return 0;
}
其他解题思路
在书上看到另一种解法,不需要进行数学变换,将输入的两个数直接视为两个字符数组,则结果就是字符数组每一位相乘的结果,而每一位对应的整数可以直接通过减'\0'
来得到。实现代码如下
#include <iostream>
#include <string.h>
#define MAX 10
using namespace std;
int main()
{
char a[MAX], b[MAX];
while(cin>>a>>b){
int res = 0;
int s1, s2;
s1 = strlen(a);
s2 = strlen(b);
int i,j;
for(i=0; i<s1; i++){
for(j=0; j<s2; j++){
res += (a[i] - '0') * (b[j] - '0');
}
}
cout<<res<<endl;
}
return 0;
}
练习题1:反序数
题目描述
设N是一个四位数,它的9倍恰好是其反序数(例如:1234的反序数是4321)
求N的值
输入描述
程序无任何输入数据。
输出描述:
输出题目要求的四位数,如果结果有多组,则每组结果之间以回车隔开。
示例1
输入:
无
输出:
无
解题思路
这题比较简单,我是把这个数字读取为字符串,然后对字符串进行操作得到反序数,最后再进行比较的。代码如下:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int main()
{
int i, j, iRe;
char num[5];
char numRe[5];
for(i=1000; i<10000; i++){
sprintf(num, "%d", i);
for(j=0; j<4; j++){
numRe[j] = num[3 - j];
}
iRe = atoi(numRe);
if(iRe == 9 * i){
cout<<i<<" ";
}
}
return 0;
}
其他解题思路
在牛客网上面看到过改进思路,其实这个地方不需要遍历到10000,直接遍历到1111就可以,因为大于1111的数,其9倍一定是五位数。
练习题2:对称平方数
题目描述
打印所有不超过n(n<256)的,其平方具有对称性质的数。如11*11=121。
输入描述
无
输出描述:
每行一个数,表示对称平方数。
示例1
输入:
无
输出:
无
解题思路
这题比较简单,在这题我同样是遍历256以内的所有数,同时用字符串处理解决的。
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
int i, j, iDou;
char ch[8];
for(i=1; i<256; i++){
iDou = i * i;
sprintf(ch, "%d", iDou);
int cmt = 0;
while(iDou > 0){
cmt++;
iDou /= 10;
}
for(j=0; j<cmt/2; j++){
if(ch[j] != ch[cmt - j -1]){
break;
}
}
if(j == cmt / 2){
cout<<i<<endl;
}
}
return 0;
}
其他解题思路
以上我都是使用字符串来事先的求一个数的反序数,但其实也可以用数学形式求得,也可以学习一下,代码示例如下,来源:https://www.nowcoder.com/questionTerminal/1b79865e2f534db0adba84dca10d6bae
int rev(int n){//反转整数
int new_n=0;
while(n){
new_n = new_n*10+n%10;
n/=10;
}
return new_n;
}
练习题3:Digital Roots
题目描述
The digital root of a positive integer is found by summing the digits of the integer. If the resulting value is a single digit then that digit is the digital root. If the resulting value contains two or more digits, those digits are summed and the process is repeated. This is continued as long as necessary to obtain a single digit.
For example, consider the positive integer 24. Adding the 2 and the 4 yields a value of 6. Since 6 is a single digit, 6 is the digital root of 24. Now consider the positive integer 39. Adding the 3 and the 9 yields 12. Since 12 is not a single digit, the process must be repeated. Adding the 1 and the 2 yeilds 3, a single digit and also the digital root of 39.
输入描述
The input file will contain a list of positive integers, one per line.
The end of the input will be indicated by an integer value of zero.
输出描述:
For each integer in the input, output its digital root on a separate
line of the output.
示例1
输入:
24
39
0
输出:
6
3
解题思路
这题其实如果只考虑思路的话,还是挺简单的,但是一开始一直AC不了,题目中只说了输入数据是正整数,但是没有限定范围,考虑可能是大数问题,所以需要使用字符数组来解决,而且这个大数还不是一般的大,一开始我是限定数组大小为1000,报RuntimeError,后来把数组设置为10000才AC。
#include <iostream>
#include <string.h>
#include <stdio.h>
#define MAX 10000
using namespace std;
char a[MAX];
char root[MAX];
char temp[MAX];
int main()
{
while(cin>>a){
int length = strlen(a);
if(length == 1 && a[0] == '0'){
break;
}
//how to copy string or the array of char
strcpy(root, a);
int length2 = strlen(root);
while(length2 > 1){
strcpy(temp, root);
int x = 0, j = 0;
for(j=0; j<length2; j++){
x += root[j] - '0';
}
sprintf(root, "%d", x);
length2 = strlen(root);
}
cout<<root<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 在题目中没有明确规定数据范围时需要考虑大数问题,在对很大的数进行处理时,可能long long也不够,这时候最好考虑使用字符数组。
- 对于C语言中的字符数组复制不能直接使用
=
,而需要使用char* strcpy(char* dst, const char* src);
函数。同时对于C++中是string对象的拷贝有浅拷贝(只赋值地址,会改变原变量的值)和深拷贝(重新开辟存储空间,对数据进行赋值)之分,在拷贝时直接使用=
,默认是深拷贝,其他细节可见:https://blog.youkuaiyun.com/tattooe/article/details/78444815
机试指南练习总结第四章-进制转换
例4.2:又一版A+B
题目描述
输入两个不超过整型定义的非负10进制整数A和B(<=231-1),输出A+B的m (1 < m <10)进制数。
输入描述
输入格式:测试输入包含若干测试用例。每个测试用例占一行,给出m和A,B的值。
当m为0时输入结束。
输出描述:
输出格式:每个测试用例的输出占一行,输出A+B的m进制数。
示例1
输入:
8 1300 48
2 1 7
0
输出:
2504
1000
解题思路
这题同样比较简单,但是要注意两个问题,要不然也无法AC
- 数据的范围,题目中说A,B都是int范围内的数,但是A,B之和就不一定在int范围内,所以此时需要使用long long来对数据进行存储。
- 需要特别考虑A和B都为0的情况,我一开始就没有考虑A,B都为0的情况,所以无法AC,不过是否要特殊判定也需要视代码结构而定。。
#include <iostream>
#define MAX 33
using namespace std;
int res[MAX];
//the range of input
//when a == b == 0
//the usage of long long, when using scanf or printf for input or output, can not use %d
int main()
{
long long a, b;
int m;
while(cin>>m){
if(m == 0){
break;
}
cin>>a>>b;
if(a == 0 && b == 0){
cout<<"0"<<endl;
continue;
}
long long sum = a + b;
int cmt = -1;
while(sum > 0){
res[++cmt] = sum % m;
sum /= m;
}
for(; cmt>=0; cmt--){
cout<<res[cmt];
}
cout<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 如果使用
scanf
和printf
对long long
类型的数据进行输入输出,不能使用%d
,而需要使用%lld
或者%I64d
,不过在一些平台上%lld
无法通过,所以还是一致使用%I64d
。来源:https://blog.youkuaiyun.com/u014665013/article/details/38369509
例4.3:数制转换
题目描述
求任意两个不同进制非负整数的转换(2进制~16进制),所给整数在long所能表达的范围之内。 不同进制的表示符号为(0,1,…,9,a,b,…,f)或者(0,1,…,9,A,B,…,F)。
输入描述
输入只有一行,包含三个整数a,n,b。a表示其后的n 是a进制整数,b表示欲将a进制整数n转换成b进制整数。
a,b是十进制整数,2 =< a,b <= 16。
数据可能存在包含前导零的情况。
输出描述:
可能有多组测试数据,对于每组数据,输出包含一行,该行有一个整数为转换后的b进制数。
输出时字母符号全部用大写表示,即(0,1,...,9,A,B,...,F)。
示例1
输入:
15 Aab3 7
输出:
210306
解题思路
先把n转换为10进制数,然后从十进制转换到对应进制。对字母的处理可以根据ASCII码表进行。
#include <iostream>
#include <string.h>
#include <math.h>
#include <stdio.h>
#define MAX 30
using namespace std;
//when there is 0 before the data
//the usage of map
//the order of char array
int main()
{
int a, b, i;
char str[MAX];
char res[MAX];
while(cin>>a>>str>>b){
int s = strlen(str);
long long temp = 0;
for(i=s-1; i>=0; i--){
if(str[i] >= '0' && str[i] <= '9'){
temp += (str[i] - '0') * (int )(pow(a, s - i - 1) + 0.5);
}else{
temp += (str[i] - ((str[i] > 'Z') ? 'a' : 'A') + 10) * (int )(pow(a, s - i - 1) + 0.5);
}
}
int d;
int cmt = -1;
while(temp > 0){
d = temp % b;
if(d > 9){
res[++cmt] = 'A' + (d - 10);
}else{
cmt++;
sprintf(res + cmt, "%d", d);
}
temp /= b;
}
for(; cmt >= 0; cmt--){
cout<<res[cmt];
}
cout<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 在读取整数时如果存在前导0,不影响数据大小,即输入001和1效果相同。
- 对于字符数组和string来说,索引为0的数据就是最左边的数据,索引从左至右逐渐增大。
- 简单记忆ASCII码表,数字’0’对应的十进制数为48,大写字母’A’对应的十进制数为65,小写字母’a’对应的十进制数为97。来源:https://blog.youkuaiyun.com/qq_35831134/article/details/90484568
练习题1:进制转换
题目描述
将一个长度最多为30位数字的十进制非负整数转换为二进制数输出。。
输入描述
多组数据,每行为一个长度不超过30位的十进制非负整数。
(注意是10进制数字的个数可能有30个,而非30bits的整数)
输出描述:
每行输出对应的二进制数。
示例1
输入:
0
1
3
8
输出:
0
1
11
1000
解题思路
本题思路比较简单,还是利用%运算来解决,主要问题是数据范围太大,整数的长度最多为30位,肯定需要使用字符数组来存储,而对于字符数组来说最大的问题就是实现除法运算,具体代码如下:
#include <iostream>
#include <stdio.h>
#include <string.h>
#define MAX 32
//the problem of sprintf
using namespace std;
char str[MAX];
char res[MAX * 10];
char temp[MAX];
void division(char a[], int n, char res[]){
int i;
int r = 0;
for(i=0; i<n; i++){
//r 既作余数又作被除数
r = a[i] - '0' + r * 10;
if(r < 2){
res[i] = '0';
continue;
}
sprintf(res + i, "%d", r / 2);
r = r % 2;
}
}
int main()
{
while(cin>>str){
int n = 1;
int i;
int s = strlen(str);
int cmt = 0;
while(n > 0){
sprintf(res + cmt, "%d", (str[s-1] - '0') % 2);
cmt++;
division(str, s, temp);
strcpy(str, temp);
n = 0;
for(i=0; i<s; i++){
if(str[i] != '0'){
n++;
}
}
}
cmt--;
for(; cmt >= 0; cmt--){
cout<<res[cmt];
}
cout<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 这题一开始一直没有AC,后来发现是使用
sprintf
的原因,因为原来的代码中结果和原始的数放在同一个字符数组中,但是如果使用sprintf(s, "%d", x)
的话,sprintf
函数会自动在s的后一个位置加上'\0'
,这样就覆盖了还未处理的数据,产生了错误,这点以后要特别注意。
练习题2:八进制
题目描述
输入一个整数,将其转换成八进制数输出
输入描述
输入包括一个整数N(0<=N<=100000)。
输出描述:
可能有多组测试数据,对于每组数据,
输出N的八进制表示数。
示例1
输入:
7
8
9
输出:
7
10
11
解题思路
本题是一个水题,没有多少要注意的地方,直接按普通的进制转换就能AC,代码如下
#include <iostream>
#include <stdio.h>
#define MAX 25
using namespace std;
char res[MAX];
int main()
{
int n;
while(cin>>n){
int cmt = 0;
while(n > 0){
sprintf(res + cmt, "%d", n % 8);
cmt++;
n /= 8;
}
cmt--;
for(; cmt >= 0; cmt--){
cout<<res[cmt];
}
cout<<endl;
}
return 0;
}
机试指南练习总结第四章-最大公约数(GCD)
一般来讲求最大公约数都会使用欧几里得算法(辗转相除法),原理即为a
, b
的最大公约数等于b
, a mod b
的最大公约数(证明可见书),其中需要注意的是,若a
, b
中有一个数为0, 则这两个数的最大公约数就是另外一个数,这也是欧几里得算法的递归出口。
例题4.4:最大公约数
题目描述
输入两个正整数,求其最大公约数。
输入描述
测试数据有多组,每组输入两个正整数。
输出描述:
对于每组输入,请输出其最大公约数。
示例1
输入:
49 14
输出:
49 14
解题思路
直接使用辗转相除法就能解决。
#include <iostream>
using namespace std;
int gcd(int a, int b){
int temp;
if(a < b){
temp = a;
a = b;
b = temp;
}
if(b == 0){
return a;
}
return gcd(b, a % b);
}
int main()
{
int a, b;
while(cin>>a>>b){
cout<< gcd(a, b) <<endl;
}
return 0;
}
机试指南练习总结第四章-最小公倍数(LCM)
最小公倍数可直接用a
, b
两个数的乘积除以gcd(a, b)
得到(证明可见书)
例题4.4:最小公倍数
题目描述
输入两个整数n,m。1\leq n,m\leq 1.5\times10^51≤n,m≤1.5×10
5。答案确保在int范围以内。
输入描述
输出两个数的最小公倍数。
输出描述:
输出两个数的最小公倍数。
示例1
输入:
6 4
输出:
12
解题思路
比较简单,直接用乘积除以最大公约数就能求得,但是考虑题目中的数据范围,使用了long long 类型来存储数据。
#include <iostream>
using namespace std;
long long gcd(long long a, long long b){
long long temp;
if(a < b){
temp = a;
a = b;
b = temp;
}
if(b == 0){
return a;
}
return gcd(b, a % b);
}
int main()
{
long long n, m;
cin>>n>>m;
long long temp = n * m;
int g = gcd(n, m);
cout<<temp / g<<endl;
return 0;
}
练习题: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.
示例1
输入:
2
3 5 7 15
6 4 10296 936 1287 792 1
输出:
105
10296
解题思路
首先,要说明的是我一开始看错题了,以下代码应该是不能AC的,但是在ZOJ上确实AC了,我是在做完之后看别人的博客的时候才发现自己看错题了,笑死我了,因为是英文题目,我一开始把每行的第一个数据也看成是求最小公倍数的数据集中的数了。导致我一开始做的时候,发现不知道每组数据一共有多少个数,还想了好久来解决这个问题。以下代码也是按这个思路做的,虽然看错题目了,但是在这个过程中对于C/C++中cin,get,getline等输入方式都有了更深的理解,也算是因祸得福。
总体来说,这题的思路其实比较简单,就是首先求两个数的最小公倍数,然后用这个最小公倍数和第三个数继续求。
#include <iostream>
#include <string.h>
#include <stdlib.h>
#define MAX 10000
using namespace std;
//how to implement input
//cin.getline will not influence strlen
//the misture of cin and cin.getline
unsigned long long data[MAX];
unsigned long long gcd(unsigned long long a, unsigned long long b){
unsigned long long temp;
if(a < b){
temp = a;
a = b;
b = temp;
}
if(b == 0){
return a;
}
return gcd(b, a % b);
}
unsigned long long lcm(unsigned long long a, unsigned long long b){
return a * b / gcd(a, b);
}
int in(unsigned long long data[]){
char str[MAX];
unsigned long long res = 0;
cin.getline(str, MAX);
int s = strlen(str);
int i, cmt = 0;
for(i=0; i<s; i++){
if(str[i] >= '0' && str[i] <= '9'){
res *= 10;
res += str[i] - '0';
}
if(str[i] == ' ' || i == s-1){
data[cmt] = res;
res = 0;
cmt++;
}
}
return cmt;
}
int main()
{
int n, i, j;
cin>>n;
cin.ignore(1);
for(i=0; i<n; i++){
int s = in(data);
unsigned long long res = data[0];
for(j=1; j<s; j++){
res = lcm(res, data[j]);
}
cout<<res<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 上面提到的,当一组数据为一行,但是不知道每行有多少输入数据的时候要如何进行输入,可以先将这一行数据读成一个字符数据,然后再进行处理。可见上面代码中的
int in(unsigned long long data[])
。其中还产生了一个问题,就是读入数据时残余的换行符问题,相关问题已经在我的另一篇博客中总结:https://blog.youkuaiyun.com/qq_41911859/article/details/107690519 - 我上面是使用
unsigned long long
来存储数据,避免溢出的,但是在有时候这样也可能会溢出,原因就是运算的顺序,在计算最小公倍数的时候,如果先做乘法再做除法就很可能会溢出,下次遇到这种情况可以先做除法再做乘法来避免溢出。
其他解题思路
上面的代码是我看错题目的代码,下面附上一个正解。来源:https://blog.youkuaiyun.com/qq_43920552/article/details/88936922?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight
#include<iostream>
using namespace std;
int lcm(int a,int b)
{
int t;
int a1=a;
int b1=b;
if(a==0||b==0)
return a>b?a:b;
if(b>a)
{
t=a;
a=b;
b=t;
}
while(b)
{
t=b;
b=a%b;
a=t;
}
return a1/a*b1;
}
int main()
{
int n,t,temp,num;
cin>>n;
while(n--)
{
cin>>t;
temp=1;
for(int i=0;i<t;i++)
{
cin>>num;
temp=lcm(temp,num);
}
cout<<temp<<endl;
}
return 0;
}
机试指南练习总结第四章-素数筛法
素数是指只能被1和其本身整除的正整数。关于素数的判定主要有两种方法.同时需要记住的是,0,1,和负数都不是素数。
- 对于正整数n, 遍历从2到
sqrt(n)
的所有正整数,如果都不能整除n,则n就是一个素数。 - 对于非素数而言,其一定有一个比它小的素数为它的因数。所以对于正整数n,如果比它小的素数及其所有倍数都被打上标记,但是n没有被打上标记,也能够说明正整数n为素数。
例4.6:素数判定
题目描述
给定一个数n,要求判断其是否为素数(0,1,负数都是非素数)。
输入描述
测试数据有多组,每组输入一个数n。
输出描述:
对于每组输入,若是素数则输出yes,否则输入no。
示例1
输入:
13
输出:
yes
解题思路
这题比较简单,按照第一种素数判定方法就能解决。
#include <iostream>
#include <math.h>
using namespace std;
//the usage of sqrt and (int)
int main()
{
int n;
while(cin>>n){
if(n <= 1){
cout<<"no"<<endl;
continue;
}
int temp = (int)sqrt(n);
int i;
for(i=2; i<=temp; i++){
if(n % i == 0){
cout<<"no"<<endl;
break;
}
}
if(i > temp){
cout<<"yes"<<endl;
}
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- C/C++中的
double sqrt(double);
函数问题,此函数输入和输出都属double类型,在使用时一般都需要使用int进行强制类型转换,同时sqrt函数计算比较耗时,不推荐在循环计算,可以使用临时变量,以空间换时间。
例4.7:素数
题目描述
输入一个整数n(2<=n<=10000),要求输出所有从1到这个整数之间(不包括1和这个整数)个位为1的素数,如果没有则输出-1。
输入描述
输入有多组数据。
每组一行,输入n。
输出描述:
输出所有从1到这个整数之间(不包括1和这个整数)个位为1的素数(素数之间用空格隔开,最后一
个素数后面没有空格),如果没有则输出-1。
示例1
输入:
100
输出:
11 31 41 61 71
解题思路
这题可以使用上面提到的第二种方法进行求解,以下是我的代码,但是我的思路是绕了弯子的,我在判定一个数是否为素数的时候设置了3种状态,是素数、不是素数、和未判定,还使用了第一种方法来判定未判定的数是否是素数。但其实这种思路不大对,对第二种素数判定方法的理解不透彻,因为未判定的整数一定就素数,因为它没有比自己小的素因数。
#include <iostream>
#include <string.h>
#include <math.h>
#define MAX 10000
using namespace std;
//the initialization of array
//the usage of memset
//bool data
char data[MAX];
bool isPrime(int n){
if(n <= 1)
return false;
int i;
int temp = (int)sqrt(n) + 1;
for(i=2; i<temp; i++){
if(n % i == 0)
return false;
}
return true;
}
int main()
{
int n;
memset(data, 'u', sizeof(data));
while(cin>>n){
int i;
int cmt = 0;
for(i=1; i<n; i++){
if(data[i] == 'f')
continue;
if(data[i] == 't' && i % 10 == 1){
if(cmt == 0){
cout<<i;
}else{
cout<<" "<<i;
}
cmt++;
}
if(data[i] == 'u'){
bool flag = isPrime(i);
if(flag == true){
data[i] = 't';
if(i % 10 == 1){
if(cmt == 0){
cout<<i;
}else{
cout<<" "<<i;
}
cmt++;
}
int j = 2 * i;//the problem
for(; j<= n; j += i){
data[j] = 'f';
}
}else{
data[i] = 'f';
}
}
}
if(cmt == 0){
cout<<-1;
}
cout<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- C++中的bool类型,C++中的bool类型占一个字节的长度,有true和false两种值,其中0,代表false,非0代表true,所以在为bool变量初始化时可以直接使用0,或1,同时如果未对bool变量进行初始化,则bool变量的默认值根据编译器不同也会不同。参考博客:https://blog.youkuaiyun.com/liunan199481/article/details/84100110
void *memset(void *s, int v, size_t n);
函数的使用。该函数包含在头文件<string.h>中,同时需要特别注意的是,memset
函数是按字节赋值的,所以函数第3个参数需要输入的是字节数。同时这也就意味着,memset
函数只能为数据长度为1个字节的数据类型赋初值(例如char类型和bool类型),而对于int、double等多个字节的数据,就只能初始化为0,或者-1。参考博客:https://www.cnblogs.com/yhlboke-1992/p/9292877.html- sizeof 和STL中的length()和size()函数的区别,sizeof是操作符,计算是是该类型的字节数,单位是字节。而另外两个是函数,计算的是有多少个该类型的数,单位为该类型的长度。
其他解题思路
根据以上说到的我的代码的问题,我重新写了一遍代码,其中要特别注意的是,当i
为素数时,不需要从2*i
开始对其倍数进行标记,直接从i* i
开始进行标记即可,因为如果存在k* i(k<i)
,则这个数在对k
进行处理时就已经标记过了。
#include <iostream>
#include <string.h>
#include <vector>
#define MAX 10002
using namespace std;
bool data[MAX];
int res[MAX];
int main()
{
memset(data, 1, sizeof(data));
int i;
int cmt = 0;
for(i=2; i<MAX; i++){
if(data[i] == true){
int j;
for(j=i*i; j<=MAX; j+=i){
data[j] = false;
}
res[cmt] = i;
cmt++;
}
}
int n;
while(cin>>n){
bool flag = false;
for(i=0; i<cmt; i++){
if(res[i] >= n){
break;
}else if(res[i] % 10 == 1){
flag ? cout<<" "<<res[i] : cout<<res[i];
flag = true;
}
}
if(!flag){
cout<<-1;
}
cout<<endl;
}
return 0;
}
机试指南练习总结第四章-分解素因数
分解素因数问题具体可见例4.8
例4.8:质因素的个数
题目描述
求正整数N(N>1)的质因数的个数。 相同的质因数需要重复计算。如
120
=
2
∗
2
∗
2
∗
3
∗
5
120=2*2*2*3*5
120=2∗2∗2∗3∗5,共有5个质因数。
输入描述
可能有多组测试数据,每组测试数据的输入是一个正整数N,(1<N<10^9)。
输出描述:
对于每组数据,输出N的质因数的个数。
示例1
输入:
120
输出:
5
解题思路
这题的基本思路就是首先使用素数筛法,把数据范围内所有可能的素因数都筛选出来,然后对于输入的n,从小到大依次与这些素数相除,最终得到1,就能够得到素因数的个数。其中需要注意的是,对于输入的整数n, 其大部分素因数都在2~sqrt(n)
的范围内,并且只可能有一个素因数大于sqrt(n)。所以对于10^9
的数据范围,我们只需要得到10^5
范围内的素数,并且如果最终相除无法得到1,说明有一个素因子大于sqrt(n),这时只需要再将最后结果加1即可得到最终结果。
#include <iostream>
#include <string.h>
#include <math.h>
#define MAX 100000
using namespace std;
bool mark[MAX];
int prime[MAX];
int main()
{
memset(mark, 1, sizeof(mark));
int cmt = 1;
int i;
long long j;
double bound = sqrt(MAX);
for(i=2; i<MAX; i++){
if(mark[i] == true){
prime[cmt] = i;
cmt++;
if(i > bound)
continue;
for(j=i*i; j<MAX; j+=i)
mark[j] = false;
}
}
int n;
while(cin>>n){
int temp = n;
int res = 0;
for(i=1; temp>1&&i<cmt; i++){
while(temp % prime[i] == 0){
temp /= prime[i];
res++;
}
}
if(temp != 1){
res++;
}
cout<<res<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 对于一个数
n
,只可能存在一个素因数大于sqrt(n)
。
其他解题思路
在牛客网上面看到一个特别简洁的思路,上面在对2~sqrt(n)范围内的数进行遍历的时候,使用了素数筛法来判断该因数是否为素数。但其实不需要判断,因为在while循环中把所有比这个数更小的素数的倍数都除完了,剩下的如果还能被整除,那一定是一个素数。反过来说,假设这个数不是素数,那这个数一定有比自己小的素因数,但是已经除以过这个素因数,所以不可能再出现这个数的整除。来源:https://www.nowcoder.com/questionTerminal/20426b85f7fc4ba8b0844cc04807fbd9
#include <iostream>
#include <cmath>
using namespace std;
int main(){
//这题的关键:
//1、是sqrt,可以极大减少复杂度,若是到方根N仍大于1,则必还有且只还有1个质因数
//2、每次瞬间整除都可帮助减少遍历范围
long M=100;
while(cin>>M)
{
long count=0;
for(long j=2;j<=sqrt(M);j++)
{
while(M%j==0)
{
M=M/j;
count++;
}
//if(M<=1)break;
}
f(M>1)count++;
cout<<count<<endl;
}
return 0;
}
例4.9:整除问题
题目描述
给定n,a求最大的k,使n!可以被
a
k
a^k
ak整除但不能被
a
(
k
+
1
)
a^(k+1)
a(k+1)整除。
输入描述
两个整数n(2<=n<=1000),a(2<=a<=1000)
输出描述:
一个整数.
示例1
输入:
6 10
输出:
1
示例2(自己想的测试用例)
输入:
100 7
输出:
16
解题思路
这道题做了比较久,主要还是因为一开始的思路不对吧。之前只是对a
分解了素因数,然后用a
的素因数和n!
中的数依次相除,但是这样就会有对于一些情况就不适用,比如n == 7, a == 21
,这时就会得到错误结果。这题的正确思路因该是对n!
和a
对进行素因数分解,然后然后求a
和n!
公共素因数的中,n!
的素因数与a
中对应公共素因数相除,求其中的最小值,即为k
。
#include <iostream>
#include <string.h>
#include <algorithm>
#define MAX 1002
using namespace std;
int pSize[MAX];//the situation of a
int tag[MAX];//the situation of n!
//when n == 100 a == 7 49 98
int main()
{
int n,a;
int i, j, temp;
while(cin>>n>>a){
memset(pSize, 0, sizeof(pSize));
memset(tag, 0, sizeof(tag));
//对n!进行素因数分解
for(i=2; i<=n; i++){
temp = i;
for(j=2; j*j<=temp; j++){
while(temp % j == 0){
temp /= j;
tag[j]++;
}
}
if(temp > 1){
tag[temp]++;
}
}
//对a进行素因数分解
temp = a;
for(i=2; i*i<=temp; i++){
while(temp % i == 0){
temp /= i;
pSize[i]++;
}
}
if(temp > 1){
pSize[temp]++;
}
int m = MAX;
for(i=2; i<=MAX; i++){
if(pSize[i] != 0 && tag[i] != 0 && (tag[i] / pSize[i] < m)){
m = tag[i] / pSize[i];
}
}
cout<<m<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 对于一个数进行素因数分解时循环中的终止条件应该为
i*i<=temp
,如果不加=
的话,对于例如49这样的数就无法得到正确结果,需要注意这样的细节。
其他解题思路
在王道书上看到另一种解题方法,大致思路也是对n!
和a
进行素因数分解,但是其中求n!
的素因数的幂指数的方法比较巧妙,值得学习。
假设对于,n!
有一个素因数p
,则在2~n
的范围内,每一个p
的倍数都能够分解出一个p
作为n!
的素因数,共有n/p
个这样的数。同时对于2-n
的范围内p*p
的倍数,每一个这样的数都可以分解出两个p
作为n!
的素因数,共有n/(p*p)
个,以此类推,则p
的幂指数为n/p+n/(p*p)+n/(p*p*p).....
,直到n/(p^n)==0
。以下我实现这种解法的代码。
#include <iostream>
#include <string.h>
#define MAX 1002
using namespace std;
int p[MAX];
int pSize[MAX];
int tag[MAX];
int main()
{
int n,a;
while(cin>>n>>a){
memset(pSize, 0, sizeof(pSize));
memset(tag, 0, sizeof(tag));
int cmt = 0;
int i, temp;
temp = a;
for(i=2; i*i<=a; i++){
//这段代码中的细节特别注意,特别容易出错!!!!
if(temp % i == 0){
cmt++;
p[cmt] = i;
}
while(temp % i == 0){
temp /= i;
pSize[cmt]++;
}
}
if(temp > 1){
cmt++;
p[cmt] = temp;
pSize[cmt]++;
}
int res = MAX;
for(i=1; i<=cmt; i++){
temp = n;
while((temp / p[i]) != 0){
tag[i] += temp / p[i];
temp /= p[i];
}
if(tag[i] / pSize[i] < res){
res = tag[i] / pSize[i];
}
}
cout <<res<< endl;
}
return 0;
}
练习题:约数的个数
题目描述
输入n个整数,依次输出每个数的约数的个数
输入描述
输入的第一行为N,即数组的个数(N<=1000)
接下来的1行包括N个整数,其中每个数的范围为(1<=Num<=1000000000)
输出描述:
可能有多组输入数据,对于每组输入数据,
输出N行,其中每一行对应上面的一个数的约数的个数。
示例1
输入:
5
1 3 4 6 12
输出:
1
2
3
4
6
解题思路
这道题比较简单,直接穷举就可以得到结果,对于数n
,只需要以sqrt(n)
为界,只需要遍历1-sqrt(n)
,每找到一个约数就加2,对sqrt(n)
只需要加1,即可得到结果。
#include<iostream>
using namespace std;
int main()
{
int n, i, j;
long long num;
while(cin>>n){
for(i=0; i<n; i++){
cin>>num;
int res = 0;
for(j=1; j*j <num; j++){
if(num % j == 0){
res += 2;
}
}
if(j * j == num){
res++;
}
cout<<res<<endl;
}
}
}
其他解题思路
还有另一种思路,虽然比较复杂,但是也值得借鉴。首先对于输入的数进行素因数分解,分解为素因数
p
1
p_1
p1
p
2
p_2
p2
p
3
p_3
p3
p
4
p_4
p4…
p
n
p_n
pn,对应的幂指数为
e
1
e_1
e1
e
2
e_2
e2
e
3
e_3
e3
e
4
e_4
e4…
e
n
e_n
en,所以n的所有约数就可以视为
p
1
p_1
p1
p
2
p_2
p2
p
3
p_3
p3
p
4
p_4
p4…
p
n
p_n
pn的排列组合,而每一个素因数的对应的变化范围分别为
e
1
e_1
e1
e
2
e_2
e2
e
3
e_3
e3
e
4
e_4
e4…
e
n
e_n
en,所以可知约数的总数为
(
e
1
+
1
)
∗
(
e
1
+
1
)
∗
(
e
2
+
1
)
∗
(
e
3
+
1
)
∗
(
e
4
+
1
)
∗
.
.
.
.
∗
(
e
n
+
1
)
(e_1+1)*(e_1+1)*(e_2+1)*(e_3+1)*(e_4+1)*....*(e_n+1)
(e1+1)∗(e1+1)∗(e2+1)∗(e3+1)∗(e4+1)∗....∗(en+1),由此可得对应代码如下
#include <iostream>
#include <string.h>
#define MAX 100002
#define MAXN 1002
using namespace std;
int prime[MAX];
int pSize[MAX];
int main()
{
int n, i, j, cmt;
long long num ,temp;
while(cin>>n){
for(j=0; j<n; j++){
memset(pSize, 0, sizeof(pSize));
cin>>num;
temp = num;
cmt = 0;
//这段代码要特别注意,很容易出错,测试是否出错的用例,49 和4,100
for(i=2; i*i<=temp; i++){
if(temp % i == 0){
cmt++;
prime[cmt] = i;
}
while(temp % i == 0){
temp /= i;
pSize[cmt]++;
}
}
if(temp > 1){
cmt++;
prime[cmt] = temp;
pSize[cmt]++;
}
int res = 0;
if(cmt != 1){
temp = 1;
for(i=1; i<=cmt; i++){
temp *= (pSize[i] + 1);
}
res = temp;
}else{
res = pSize[1] + 1;
}
cout<<res<<endl;
}
}
return 0;
}
机试指南练习总结第四章-二分求幂
一种快速求解 a b a^b ab的方法,例如求解 1 0 7 10^7 107,如果直接计算的的话,需要计算7次乘法,比较麻烦,注意到 1 0 7 10^7 107 = 1 0 1 10^1 101* 1 0 2 10^2 102* 1 0 4 10^4 104 ,而可以通过 1 0 1 10^1 101平方求得 1 0 2 10^2 102和 1 0 4 10^4 104,这样就乘法的次数就减少到4次,当b较大时,节省的时间更加明显。而只需要将b转换为二进制形式就能够求得 a b a^b ab的分解形式
例4.10:人见人爱A^B
题目描述
求A^B的最后三位数表示的整数。
说明:A^B的含义是“A的B次方”
输入描述
输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B<=10000)
如果A=0, B=0,则表示输入数据的结束,不做处理。
输出描述:
对于每个测试实例,请输出A^B的最后三位表示的整数,每个输出占一行。
示例1
输入:
2 3
12 6
6789 10000
0 0
输出:
8
984
1
解题思路
在本题中,A和B的数据范围较大,不可能直接计算A^B,也无法存储。需要考虑的最重要的一点就是,乘法中,最后三位数字只与两个乘数的最后三位数字有关,所以在整个计算过程中,中间变量和最后结果可以只保存数字的最后三位,这样就能在数据范围内存储数据,同时由于B的数据范围较大,所以可以使用二分求幂的方法来计算。具体的代码如下:
#include <iostream>
using namespace std;
int main()
{
int a, b;
while(cin>>a>>b)
{
if(a == 0 && b == 0)
break;
if(b == 0){
cout<<1<<endl;
}
int temp = 1;
int x = a % 1000;
while(b > 0)
{
if(b % 2 == 1)
{
temp = (temp * x) % 1000;
}
cout<<"temp = "<<temp<<" x = "<<x<<endl;
x = (x * x) % 1000;
b /= 2;
}
cout<<temp<<endl;
}
return 0;
}
练习题1:A sequence of numbers
题目描述
Xinlv wrote some sequences on the paper a long time ago, they might be arithmetic or geometric sequences. The numbers are not very clear now, and only the first three numbers of each sequence are recognizable. Xinlv wants to know some numbers in these sequences, and he needs your help.
输入描述
The first line contains an integer N, indicting that there are N
sequences. Each of the following N lines contain four integers. The
first three indicating the first three numbers of the sequence, and the
last one is K, indicating that we want to know the K-th numbers of the
sequence.
You can assume 0 < K <= 10^9, and the other three numbers are in the
range [0, 2^63). All the numbers of the sequences are integers. And the
sequences are non-decreasing.
输出描述:
Output one line for each test case, that is, the K-th number module (%) 200907.
示例1
输入:
2
1 2 3 5
1 2 4 5
输出:
5
16
解题思路
该题为等差和等比数列求和,只需要使用等差数列和等比数列的求和公式就能得到结果,但是需要注意的是,这里的数据范围都比较大,需要使用long long的数据类型来存储,要注意的是中间变量也需要使用long long的数据类型。最后对于等比数列求和需要使用快速求幂的方法。代码如下:
#include <iostream>
#define MAX 5
#define m 200907
using namespace std;
//大数数据范围的处理
//超出数据范围时的比较
long long myPow(long long a, long long b)
{
long long temp = 1;
long long x = a % m;
while(b > 0)
{
if(b % 2 == 1)
temp = (temp * x) % m;
x = (x * x) % m;
b /= 2;
}
return temp;
}
int main()
{
int n, i;
long long data[MAX];
long long k;
long long x;
cin>>n;
for(i=0; i<n; i++)
{
long long res;
cin>>data[0]>>data[1]>>data[2]>>k;
if(data[2] - data[1] == data[1] - data[0])
{
x = (data[2] - data[1]) % m;
res = ((data[0] % m) + ((k - 1) % m) * x) % m;
//cout<<"x = "<<x<<" res = "<<res<<endl;
}
else
{
x = (data[1] / data[0]) % m;
res = ((data[0] % m) * myPow(x, k-1)) % m;
}
cout<<res<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 对于这样的大范围数据需要注意的是,两个操作数的运算结果首先会保存在一个和操作数大小相同的数据空间中,然后才会再被赋值给另一个变量。实验代码如下,所以当一个操作数的数据范围在一个数据类型的边界附近时,如果还要对该操作数进行运算,则最后设置更大的存储空间,避免溢出。
#include<iostream>
#include <math.h>
using namespace std;
int main()
{
/*
int x = 100000000;
int y = 1000;
long long res = x * y;
cout<<res<<endl;//输出 1215752192, 显然产生了溢出
*/
long long x = 100000000;
long long y = 1000;
long long res = x * y;
cout<<res<<endl;//输出 100000000000, 正确结果
}
练习题2:Tr A
题目描述
A为一个方阵,则Tr A表示A的迹(就是主对角线上各项的和),现要求Tr(A^k)%9973。
输入描述
数据的第一行是一个T,表示有T组数据。
每组数据的第一行有n(2 <= n <= 10)和k(2 <= k < 10^9)两个数据。接下来有n行,每行有n个数据,每个数据的范围是[0,9],表示方阵A的内容。
输出描述:
对应每组数据,输出Tr(A^k)%9973。
示例1
输入:
2
2 2
1 0
0 1
3 99999999
1 2 3
4 5 6
7 8 9
输出:
2
2686
解题思路
这题其实思路挺简单的,实现矩阵乘法和二分求幂就可以解决,但是这个地方有一个需要特别注意的地方,如下代码,求矩阵求幂的时候需要先将存放最后结果的矩阵res初始化为单位矩阵,而不是0矩阵和全1矩阵,这点需要特别注意。
#include <iostream>
#include <string.h>
#define MAX 12
#define M 9973
using namespace std;
int data[MAX][MAX];
int mulres[MAX][MAX]; //存储矩阵相乘的结果
int res[MAX][MAX];
//res的初始化
void multiple(int a[][MAX], int b[][MAX], int n)
{
int i, j, k;
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
for(k=0; k<n; k++)
{
mulres[i][j] = ((a[i][k] % M) * (b[k][j] % M) + mulres[i][j]) % M;
}
}
}
}
int main()
{
int t, n, k, i, j, q;
cin>>t;
for(q=0; q<t; q++)
{
cin>>n>>k;
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
cin>>data[i][j];
}
}
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
if(i == j)
res[i][j] = 1;
else
res[i][j] = 0;
}
}
while(k > 0)
{
if(k % 2 == 1)
{
multiple(res, data, n);
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
res[i][j] = mulres[i][j];
}
}
}
memset(mulres, 0, sizeof(mulres));
k /= 2;
multiple(data, data, n);
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
data[i][j] = mulres[i][j];
}
}
memset(mulres, 0, sizeof(mulres));
}
int x = 0;
for(i=0; i<n; i++)
{
x = (res[i][i] + x) % M;
}
cout<<x<<endl;
}
return 0;
}
在这段代码中还有一些需要总结的知识点:
- 将二维数组作为函数的参数的问题,C语言中二维数组是按行存储的,在将二维数组作为函数参数时必须指定列数,行数可以不指定。可以体现为
void Func(int array[3][10]);
,void Func(int array[][10]);
和void print(int (*a)[3]);
等形式。参考博客:https://www.cnblogs.com/Anker/archive/2013/03/09/2951878.html
机试指南练习总结第四章-高精度整数
王道指南上面介绍了一种存储大整数的数据结构,如下。一般来讲,对于大整数的操作除了存储结构外,在进行输入时还需要配合字符数组进行输入,然后再转为整数。
struct biginterger
{
int digit[MAX];//其中一个数据单位可以存储原来数据中的几位数,例如d[0] = 1234,存储了原来整数中的四位数
int size;//用来保存digit数组中已使用的长度
}
在大整数的位数不是特别多时,例如小与10000,可以直接使用int
数组来进行存储,这样的话在对相关数据进行操作的时候会更加简便。
例4.11:a+b
题目描述
实现一个加法器,使其能够输出a+b的值。
输入描述
输入包括两个数a和b,其中a和b的位数不超过1000位。
输出描述:
可能有多组测试数据,对于每组数据,
输出a+b的值。
示例1
输入:
2 6
10000000000000000000 10000000000000000000000000000000
输出:
8
10000000000010000000000000000000
解题思路
经典的大整数运算,首先可以使用王道上面的存储结构来进行求解,如下。在使用这种解法时需要特别注意几个问题
- 因为digit数组中一个数代表原来是四位,所以在输出时,如果数据小与1000,需要在前面补0
- 这种存储结构较为复杂,数组的低位和字符串低位高位的对应,以及在进行加法运算时的对应问题,这些都要特别注意,很容易出错
- 在进位时需要特别注意
#include <iostream>
#include <string.h>
#define MAX 1002
//补0的问题
//从低位加起和从高位加起的区别
//进位的问题
using namespace std;
struct biginterger
{
int digit[MAX];
int s;
};
int main()
{
char as[MAX], bs[MAX];
int i, j;
while(cin>>as>>bs)
{
int al = strlen(as);
int bl = strlen(bs);
struct biginterger a;
struct biginterger b;
memset(a.digit, 0, sizeof(a.digit));
memset(b.digit, 0, sizeof(b.digit));
int cmt = (al % 4 == 0) ? al / 4 : al / 4 + 1;
int total = 1;
for(i=0; i<cmt; i++)
{
int digit = 0, p = 1;
for(j=0; j<4 && total<=al; j++)
{
digit = digit + (as[al-total] - '0') * p;
p *= 10;
total++;
}
a.digit[i] = digit;
}
a.s = cmt;
cmt = (bl % 4 == 0) ? bl / 4 : bl / 4 + 1;
total = 1;
for(i=0; i<cmt; i++)
{
int digit = 0, p = 1;
for(j=0; j<4 && total<=bl; j++)
{
digit = digit + (bs[bl - total] - '0') * p;
p *= 10;
total++;
}
b.digit[i] = digit;
}
b.s = cmt;
struct biginterger res;
cmt = (a.s <= b.s) ? b.s : a.s;
int carry = 0;
for(i=0; i<cmt; i++)
{
res.digit[i] = a.digit[i] + b.digit[i] + carry;
//cout<<"i = "<<i<<" carry = "<<carry<<" a.digit[i] = "<<a.digit[i]<<" b.digit[i] = "<<b.digit[i]<<" digit = "<<res.digit[i]<<endl;
carry = 0;
if(res.digit[i] > 9999)
{
carry = 1;
res.digit[i] = res.digit[i] % 10000;
}
}
if(a.s == b.s && carry == 1)
{
res.digit[cmt] = 1;
cmt++;
}
res.s = cmt;
for(i=0; i<cmt; i++)
{
int x = 1000;
while(res.digit[cmt-i-1] < x && x > 1 && i != 0)
{
cout<<'0';
x /= 10;
}
cout<<res.digit[cmt-i-1];
}
cout<<endl;
}
return 0;
}
其他解题思路
另一种解题思路就是直接将数据存储为int数组,相对来说代码简洁了很多,也不容易出错。
#include <iostream>
#include <string.h>
#define MAX 1010
using namespace std;
int a[MAX], b[MAX];
char as[MAX], bs[MAX];
int res[MAX];
void trans(char a[], int data[], int n)
{
int i;
for(i=0; i<n; i++)
{
data[i] = a[n-i-1] - '0';
}
}
int main()
{
while(cin>>as>>bs)
{
int i;
int al = strlen(as);
int bl = strlen(bs);
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(res, 0, sizeof(res));
trans(as, a, al);
trans(bs, b, bl);
int cmt = (al <= bl) ? bl : al;
int carry = 0;
for(i=0; i<cmt; i++)
{
int temp = a[i] + b[i] + carry;
carry = temp / 10;
res[i] = temp % 10;
}
if(carry != 0)
{
res[cmt] = carry;
cmt++;
}
for(i=cmt-1; i>=0; i--)
{
cout<<res[i];
}
cout<<endl;
}
}
例4.12:N的阶层
题目描述
输入一个正整数N,输出N的阶乘
输入描述
正整数N(0<=N<=1000)
输出描述:
输入可能包括多组数据,对于每一组输入数据,输出N的阶乘
示例1
输入:
4
5
15
输出:
24
120
1307674368000
解题思路
大数的乘法运算,在本题中乘法运算的一个操作数是大数,另一个操作数是能够存储在int
中的整数,在这种情况下,大整数可以使用int
数组来存储,同时可以使用这个int
整数对大整数的每一位都作乘法,并且记录进位等信息,最后就可以得到结果。
- 在本题中需要特别注意进位的问题,因为是使用
int
整数对每一位进行相乘,所以每一位的进位不一定只是一位数。在进行最后的进位时需要循环一直进位到carry == 0
为止
#include <iostream>
#include <string.h>
#define MAX 10020
using namespace std;
//进位的问题
int res[MAX];
int main()
{
int n, i, s, j;
while(cin>>n)
{
int carry = 0, temp;
memset(res, 0, sizeof(res));
res[0] = 1;
s = 1;
for(i=1; i<=n; i++)
{
carry = 0;
for(j=0; j<s; j++)
{
temp = res[j] * i + carry;
carry = temp / 10;
res[j] = temp % 10;
}
while(carry != 0)
{
res[s++] = carry % 10;
carry /= 10;
}
}
for(i=s-1; i>=0; i--)
cout<<res[i];
cout<<endl;
}
return 0;
}
其他相关的知识点
以上是一个小整数与一个大整数相乘,如果出现两个大整数相乘的情况,两个大整数都需要使用数组按位存储。在相乘时也需要按照乘法规则按位相乘,最后相加。这种情况比较复杂,以下是我在本题的情况下实现的代码。其中主要注意的两个非常容易犯错的点。
- 最后序列长度的问题
- 按位相加的问题
#include <iostream>
#include <string.h>
#define MAX 10010
using namespace std;
//序列的长度
//对应相加的问题
int res[MAX];
int temp[4][MAX];
int pluss(int data[], int s)
{
int i;
int temp = data[0] + 1;
data[0] = temp % 10;
int carry = temp / 10;
for(i=1; i<s; i++)
{
temp = data[i] + carry;
data[i] = temp % 10;
carry = temp / 10;
}
if(carry != 0)
data[s++] = 1;
return s;
}
int main()
{
int n;
while(cin>>n)
{
int i, j, k, x;
int data[4];
memset(data, 0, sizeof(data));
memset(res, 0, sizeof(res));
data[0] = 1;
res[0] = 1;
int datas = 1;
int ress = 1;
for(i=0; i<n; i++)
{
memset(temp, 0, sizeof(temp));
for(j=0; j<datas; j++)
{
int carry = 0;
for(k=0; k<ress; k++)
{
temp[j][k] = data[j] * res[k] + carry;
carry = temp[j][k] / 10;
temp[j][k] = temp[j][k] % 10;
}
if(carry != 0)
temp[j][k++] = carry;
}
memset(res, 0, sizeof(res));//注意!
int carry2 = 0;
for(x=0; x<ress+datas; x++)
{
for(j=0; j<datas; j++)
{
res[x] += ((x - j) >= 0) ? temp[j][x-j] : 0;
}
res[x] += carry2;
carry2 = res[x] / 10;
res[x] = res[x] % 10;
}
if(carry2 != 0)
{
res[x++] = carry2;
}
else if(res[x-1] == 0)
{
x--;
}
ress = x;
/*
cout<<"i = "<<i<<" res = ";
for(j=ress-1; j>=0; j--)
{
cout<<res[j];
}
cout<<" data = ";
for(j=datas-1; j>=0; j--)
{
cout<<data[j];
}
cout<<endl;
*/
datas = pluss(data, datas);
}
for(i=ress-1; i>=0; i--)
cout<<res[i];
cout<<endl;
}
return 0;
}
例4.13:进制转换
题目描述
将M进制的数X转换为N进制的数输出。注意输入时如有字母,则字母为大写,输出时如有字母,则字母为小写。
输入描述
输入的第一行包括两个整数:M和N(2<=M,N<=36)。
下面的一行输入一个数X,X是M进制的数,现在要求你将M进制的数X转换成N进制的数输出。
输出描述:
输出X的N进制表示的数。
示例1
输入:
10 2
11
输出:
1011
解题思路
我的思路就是先转化为十进制,然后再转化为对应进制,大整数使用int数组进行存储,在这个过程中要实现大整数的加法,乘法、除法和取余操作,其中除法和取余操作可以同时实现,具体代码如下
#include <iostream>
#include <string.h>
#define MAX 10000
using namespace std;
char str[MAX];
int res[MAX];
int num[MAX];
int num2[MAX];
int multiple(int data[], int a, int n)
{
int i, temp;
int carry = 0;
for(i=0; i<n; i++)
{
temp = data[i] * a + carry;
carry = temp / 10;
data[i] = temp % 10;
}
while(carry % 10 != 0)
{
data[n++] = carry % 10;
carry /= 10;
}
return n;
}
int add(int data1[], int s1, int data2[], int s2)
{
int s = (s1 >= s2) ? s1 : s2;
int i, temp, carry = 0;
for(i=0; i<s; i++)
{
temp = data1[i] + data2[i] + carry;
carry = temp / 10;
data1[i] = temp % 10;
}
if(carry != 0)
data1[s++] = carry;
return s;
}
int division(int data[], int a, int n, int &remain)//特别注意,每次做除法时要把remain清零
{
int i, temp, s = n;
int borrow = 0;
bool flag = false;
remain = 0;
for(i=n-1; i>=0; i--)
{
temp = data[i] + 10 * borrow;
if(temp < a)
{
data[i] = 0;
borrow = temp;
if(!flag)
s--;
}
else
{
flag = true;
data[i] = temp / a;
borrow = temp % a;
}
}
if(borrow != 0)
remain = borrow;
return s;
}
int main()
{
int m, n, i;
cin>>m>>n;
cin>>str;
int s = strlen(str);
memset(num, 0, sizeof(num));
memset(num2, 0, sizeof(num2));
num[0] = 1;
num2[0] = 1;
memset(res, 0, sizeof(res));
int rl = 1, nl = 1, n2l = 1;
int temp;
for(i=s-1; i>=0; i--)
{
temp = (str[i] - '9' > 0) ? (str[i] - 'A' + 10) : (str[i] - '0');
nl = multiple(num, temp, nl);
rl = add(res, rl, num, nl);
n2l = multiple(num2, m, n2l);
memset(num, 0, sizeof(num));
memcpy(num, num2, sizeof(num2));
nl = n2l;
}
i = 0;
int remain = 0;
memset(str, 0, sizeof(str));
while(rl > 1 || res[0] != 0)
{
rl = division(res, n, rl, remain);
str[i] = (remain >= 10) ? 'a' + remain - 10 : remain + '0';
i++;
}
for(i = i-1; i>=0; i--)
cout<<str[i];
cout<<endl;
return 0;
}
其他相关的知识点
memcpy()
的使用,该函数在<string.h>
头文件中,函数原型为void *memcpy(void *dest, const void *src, size_t n);
可以从src所指向的地址空间中拷贝n个字节的内容到目标地址空间中。参考博客:https://blog.youkuaiyun.com/qq_38993096/article/details/88377285- 大整数的加法、乘法、除法和取余运算的实现,可以多熟悉,之后直接使用。如下
int multiple(int data[], int a, int n)
{
int i, temp;
int carry = 0;
for(i=0; i<n; i++)
{
temp = data[i] * a + carry;
carry = temp / 10;
data[i] = temp % 10;
}
while(carry % 10 != 0)
{
data[n++] = carry % 10;
carry /= 10;
}
return n;
}
int add(int data1[], int s1, int data2[], int s2)
{
int s = (s1 >= s2) ? s1 : s2;
int i, temp, carry = 0;
for(i=0; i<s; i++)
{
temp = data1[i] + data2[i] + carry;
carry = temp / 10;
data1[i] = temp % 10;
}
if(carry != 0)
data1[s++] = carry;
return s;
}
int division(int data[], int a, int n, int &remain)//特别注意,每次做除法时要把remain清零
{
int i, temp, s = n;
int borrow = 0;
bool flag = false;
remain = 0;
for(i=n-1; i>=0; i--)
{
temp = data[i] + 10 * borrow;
if(temp < a)
{
data[i] = 0;
borrow = temp;
if(!flag)
s--;
}
else
{
flag = true;
data[i] = temp / a;
borrow = temp % a;
}
}
if(borrow != 0)
remain = borrow;
return s;
}
其他解题思路
在牛客网上面看到另一种解题思路,不需要转换为十进制,而是直接从原进制转换为对应进制。但是原理还没有完全理解,先留存一下。参考博客:https://www.nowcoder.com/questionTerminal/ae4b3c4a968745618d65b866002bbd32
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
/*M进制数转N进制,2<=M,N<=36。
输入时如有字母,则字母为大写,输出时如有字母,则字母为小写*/
int char_to_num(char c)//将字符变成数字
{
int a;
if (c >= 'A' && c <= 'Z')
a = c - 'A' + 10;
else
a = c - '0';
return a;
}
char num_to_char(int n)//将数字变成字符
{
char c;
if (n >= 10)
c = n - 10 + 'A';//结果是字符
else
c = n + '0';//结果是数字
return c;
}
string conversion(int pre, string num, int cur) //pre进制数字num转换为cur进制
{
int i = 0;
char rem;//每一步的余数,用字符型是为了方便result插入类型统一
string result;
while (i < num.size())
{
rem = '0';
for (int j = i; j < num.size(); j++)
{
int a, b;//a,b代表数字
a = char_to_num(rem);
b = char_to_num(num[j]);
int temp = (a * pre + b);//用数字计算每步结果
//还原为字符
num[j] = num_to_char(temp / cur);
rem = num_to_char(temp % cur);
}
result.insert(0, 1, rem);//头插结果
while (num[i] == '0')
i++;//跳过高位的0
}
for (int k = 0; k < result.size(); k++)//输出字母小写
if (result[k] >= 'A' && result[k] <= 'Z')
result[k] += ('a' - 'A');
return result;
}
int main()
{
int M, N;
string num;
while (cin >> M >> N)
{
cin >> num;
cout << conversion(M, num, N) << endl;
}
}