C实践习题集3之高精度运算

高精度除法

题目

给两个正整数 a,b,求 a/b的整数部分。

输入格式:

输入共两行,每行一个正整数,分别表示 a和b。 50%数据,a,b均小于1e18, 50%数据,a,b均小于1e500。

输出格式:

输出一个整数,表示a/b的整数部分。

输入样例1:

3
2

输出样例1:

1

输入样例

24781236498237462378425347823652387423654238752372365327862
8934457724628746

输出样例2:

2773669903874014740488146558678531750078864

思路

这题属于高精度除法中最复杂的类型:高精度除以高精度
对于这道题,我一开始没啥特别的思路,本来想直接使用高精度乘法和高精度减法来模拟竖式运算,但是后来发现很麻烦,然后我就去找算法。找了半天,可被我找到了(叉会儿腰)。
瞧瞧什么才是大佬(%) 高精度运算实现

大致思路和示例如下:

高精度除高精度是这几种运算中最难的一种。仍然可以模仿竖式的方式进行,过程有点儿麻烦。
另一种办法是利用减法:被除数A-除数B,看看能减多少个,直到A<B,这时剪去的个数就是商,剩下的A就是余数。如果利用前面编写好的高精度减法jianfa()和compare()的话,实现起来岂不是很容易?可惜不是这样,假设被除数A的位数M与除数B位数N之差M-N很大,这个范围有可能从0~200,按照最大的情况200考虑,这意味着商值也是高精度数字,而你要减10^200次才能减完,这是一个天文数字。

解决这个问题也很简单,我们不一个一个减,而是按照最大可能的数量级减,例如:12345/45:

//商的最大位数i=M-N+1,即4,设计一个 临时减数,减数后面补齐i-1个0,再进行减法
i=4     12345 < 45000   可以减0个   shang[4]=0      减后A:12345
i=3     12345 < 4500    可以减2个   shang[3]=2      减后A:3345
i=2     3345  < 450     可以减7个   shang[2]=7      减后A:195
i=1     195   < 45      可以减4个   shang[1]=4      减后A:15

//因shang[4]=0,故商的有效位数shang[0]--,为3
//结果  商为274,余数15

这样的计算过程,仅仅减了2+7+4=13次,而非274次,可见一个一个减是绝对不靠谱的。

完整代码:

#include<stdio.h>
#include<string.h>
void f(int dif);//循环函数 
void minus(int dif);//执行减法运算 
void copy(int N);//用于复制被除数的函数 
int judge(int*p,int dif);//判断是否进入新一轮的减法运算(第一层判断) 
char s1[505],s2[505],num[505];//s1数组存储被除数,s2数组存储除数,num数组存储商 
int len1,len2,length,first,end,flag=1;//len1是被除数的位数,len2是原始除数的位数,first用于标记被除数字符串的起点,end标记终点 
int main()
{
	int dif;//被除数和除数差几位 
	scanf("%s",s1);
	scanf("%s",s2);
	len1=strlen(s1);
	len2=strlen(s2);
	dif=len1-len2;
	//特判 
	if(dif<0){//被除数位数小于除数 
		printf("0");
		return 0;
	}
	else if(dif==0){//位数相等 
	//利用strcmp来比大小 
		if(strcmp(s1,s2)<0){
			printf("0");
			return 0;
		}
		else if(strcmp(s1,s2)==0){
			printf("1");
			return 0;
		}
	}
	first=0;
	end=len1-1;
	//使用函数 
	f(dif);
	return 0;
}
void f(int dif)
{
	int i,count,last,temp=0,store=0;//temp用作标记,除去前导0,store用作商位数的计算 
	for(i=len2,count=1;count<=dif;i++,count++) s2[i]='0';//处理除数(添0)
	last=i;//last记录处理之后的除数位数 
	s2[last]='\0';
	while(dif>=0){//当除数等于原始除数时,循环在下一轮结束 
		count=0;//刷新,计算每一轮的商 
		if(flag==0){//特判,处理商能直接整除得到且后面全是0的情况 
			if(temp!=0) num[store++]=0;
			dif--;
			continue;
		}
		if(judge(&count,dif)==1){
			while(1){//预先判断是否需要进行减法运算 
			    minus(dif);//进行减法运算 
			    count++;//完成一次次数增加一次 
			    length=end-first+1;
			    if(length<len2+dif){
				    copy(1);//被除数位数较小,整理s1
				    break; 
			    } 
			    else if(length==len2+dif){//比较被除数与除数的位数大小关系 
				    copy(1);
					if(strcmp(s1,s2)==0){
					    flag=0;
					    count++;
					    break;
				    }
				    else if(strcmp(s1,s2)<0){
					    break;
				    }
			    }
		    }
		}
		if(count!=0){
			temp=1;
		}
		if(temp!=0){
			num[store++]=count;
		}
		dif--;
		last--;
		s2[last]='\0';
	}
	for(i=0;i<store;i++){
		printf("%d",num[i]);
	}
}
void minus(int dif)//减法 
{
	int i,j,extra=0,gap,index=-1,len3=len2+dif;
	for(i=end;i>=first;i--){
		if(i-(len1-len3)>=0) gap=s1[i]-s2[i-(len1-len3)];
		else{
			gap=s1[i]-'0';
		}
		if(extra!=0) gap-=1;
		if(gap>=0){
			s1[i]=gap+'0';
			extra=0;
		}
		else{
			s1[i]=gap+10+'0';
			extra=1;
		}
		if(s1[i]=='0'){
			if(index==-1) index=i;
		}
		else index=-1;
	}
	if(index!=-1) first=index+1;//记录起点位置,即去除前导零 
}
void copy(int N)//复制字符串 OK
{
	int i,j;
	if(first!=0){
		for(i=first,j=0;i<=end;i++,j++){
		    s1[j]=s1[i];
	    }
	    first=0;
 	    end=j-1;
	    s1[j]='\0';
	}
	len1=strlen(s1);
} 
int judge(int*p,int dif)
{
	if(strlen(s1)==len2+dif){
		if(strcmp(s1,s2)==0){
			flag=0;
			(*p)++;
			return 0;
		}
		else if(strcmp(s1,s2)<0){
			return 0;
		}
		else return 1;
	}
	else if(strlen(s1)>len2+dif) return 1; 
	else return 0;
}

PS:爷差点被这题搞吐了,各种bug,写到后面发现最开始的减法函数实现有问题,还有代码行数多了,有些变量重复了都没注意。。。。害的我找半天。。。。
还好总算过了。。。。。

数楼梯

题目

楼梯有N阶,上楼可以一步上一阶,也可以一步上两阶。那么走到第N阶楼梯共有多少种不同的走法呢?

输入格式:

一个正整数 N(1<=N<=5000),表示楼梯阶数。

输出格式:

输出一个数,表示走到第N阶楼梯有多少种走法。
注意,数据范围很大,即使是64位也可能不够存。

输入样例1:

4

输出样例1:

5

输入样例2:

400

输出样例2:

284812298108489611757988937681460995615380088782304890986477195645969271404032323901

思路

关于这题,我最开始是用递归来做,不就是加上高精度加法嘛,代码很简单很好写,然后提交上去,全部超时。。。。。
我想破脑瓜也不知道除了递归怎么做,知道有人跟我说,用斐波拉契数。嗯????????(我心态崩了)

        N    1  2  3  4  5  6   7 ······
             1  2  3  5  8  13  21 ······
斐波拉契数  1  1  2  3  5  8  13  21 ······

然后就是实现功能。
就开始我只写了一个加法函数,然后用了对数组是根据常规思维来使用的,得到一个新的数后,把第二个数移到第一个数,把新数放到第二个位置。这样的结果就是有一部分数据过不了,超时。
因此,我就想到了第一个位置和第二个位置交替存放得到的新数,如下所示

位置1   位置2
  1      2
  3      2
  3      5
  8      5
  8      13
  21     13

这样交替存放(相应的需要两个加法函数),能减少原本一半的运行时间(原来的需要用到strcpy函数复制字符串,比较慢)。

完整代码

#include<stdio.h>
#include<string.h>
void add1(void);
void add2(void);
int len1,len2;
char s1[10000]={"1"},s2[10000]={"2"},temp[10000];
int main()
{
	int N,i;
	scanf("%d",&N);
	if(N<=2){
		if(N==1) puts(s1);
		else puts(s2);
	}
	else{
		for(i=3;i<=N;i++){
			len1=strlen(s1);
	        len2=strlen(s2);
			if(i%2!=0) add1();
			else add2();
		}
		if(N%2!=0) puts(s1);
		else puts(s2);
	}
	return 0;
}
void add1(void)
{
	int i,j,k,end=len2-1,sum,extra=0,flag;
	for(i=end,j=0;i>=0;i--){
		if(i-(len2-len1)>=0) sum=s2[i]-'0'+s1[i-(len2-len1)]-'0';
		else sum=s2[i]-'0';
		if(extra==1) sum++;
		if(sum<10){
			temp[j++]=sum+'0';
			extra=0;
		}
		else{
			temp[j++]=sum-10+'0';
			extra=1;
		}
	}
	if(extra!=0) temp[j++]='1'; 
	flag=j-1;
	for(i=flag,k=0;i>=0;i--,k++){
		s1[k]=temp[i];
	}
	s1[k]='\0';
}
void add2(void)
{
	int i,j,k,end=len1-1,sum,extra=0,flag;
	for(i=end,j=0;i>=0;i--){
		if(i-(len1-len2)>=0) sum=s1[i]-'0'+s2[i-(len1-len2)]-'0';
		else sum=s1[i]-'0';
		if(extra==1) sum++;
		if(sum<10){
			temp[j++]=sum+'0';
			extra=0;
		}
		else{
			temp[j++]=sum-10+'0';
			extra=1;
		}
	}
	if(extra!=0) temp[j++]='1'; 
	flag=j-1;
	for(i=flag,k=0;i>=0;i--,k++){
		s2[k]=temp[i];
	}
	s2[k]='\0';
}

然后记录这题的时候也是提醒自己,斐波拉契数原来可以在实际中这么应用。

回文数

题目

对于一个自然数n,若将n的各位数字反向排列所得的数n1与n相等,则称n为回文数,例如2332。
若给定一个N( 2<=N<=16)进制数M(M的长度在一百位以内),如果M不是回文数,可以对其进行N进制加法,最终得到回文数。
例如对于十进制数79 STEP1 : 79 + 97 = 176 STEP2 : 176 + 671 = 847 STEP3 : 847 + 748 = 1595 STEP4 : 1595 +5951 = 7546 STEP5 : 7546 + 6457 = 14003 STEP6 : 14003 + 30041 = 44044
那么对于给定的N进制数M,请判断其能否在30步以内(包括30步)得到回文数。

输入格式:

第一行包括一个正整数 N(2<=N<=16)。
第二行包括一个正整数M(一百位以内)。

输出格式:

如果可以在n步内得到回文数,输出“STEP=n”,否则输出“NO”。

输入样例1:

10
79

输出样例1:

STEP=6

输入样例2:

8
665556

输出样例2:

NO

思路

这题的关键其实就在于实现多种进制的加法运算。
还有需要注意的一点是,十进制以上的进制,用A(a),B(b),C(c),D(d),E(e)分别来表示11,12,13,14,15。所以需要在过程中加上特定字符转换的功能。
值得一提的是,由于不知道题目给出的十以上进制的数中是否含有大小写的英文字母,所以在转换时需要对大小写都做特判,然后在之后运算转换的过程中,用大写或小写来表示十以上进制的数就可以由我们来决定了。
因为是回文数,所以在做运算时比较容易(正序逆序都可)

完整代码

#include<stdio.h>
#include<string.h>
int judge(char num[]);
int ex1(char num);
char ex2(int num);
int main()
{
	int N,i,j,k,n=0,flag=0,len,sum,s1,s2,add;
	char store[105],num[105];
	scanf("%d",&N);
	scanf("%s",store);
	while(n<=30){
		strcpy(num,store);
		if(judge(num)==1){
			flag=1;
			break;
		}
		else{
			len=strlen(num);
			add=0;
			for(i=0,j=len-1,k=0;i<len;i++,j--,k++){
				s1=ex1(num[i]);
				s2=ex1(num[j]);
				sum=s1+s2;
				if(add==1){
					sum+=1;
					add=0;
				}
				if(sum<N) store[k]=ex2(sum);
				else{
					store[k]=ex2(sum-N);
					add=1;
				}
			}
			if(add!=0) store[k++]='1';
			store[k]='\0';
		}
		n++;
	}
	if(flag==1) printf("STEP=%d",n);
	else printf("NO");
	return 0;
}
int judge(char num[])
{
	int i,j;
	for(i=0,j=strlen(num)-1;i<j;i++,j--){
		if(num[i]!=num[j]) break;
	}
	if(i>=j) return 1;
	else return 0;
}
int ex1(char num)//字符转数字 
{
	if('0'<=num&&num<='9') return num-'0';
	else{
		if(num=='A'||num=='a') return 10;
		else if(num=='B'||num=='b') return 11;
		else if(num=='C'||num=='c') return 12;
		else if(num=='D'||num=='d') return 13;
		else if(num=='E'||num=='e') return 14;
		else if(num=='F'||num=='f') return 15;
	}
}
char ex2(int num)
{
	if(0<=num&&num<=9) return num+'0';
	else{
		if(num==10) return 'a';
		else if(num==11) return 'b';
		else if(num==12) return 'c';
		else if(num==13) return 'd';
		else if(num==14) return 'e';
		else if(num==15) return 'f';
	}
}

posted @ 2020-03-14 19:21  枭魈  阅读(440)  评论(0编辑  收藏  举报