题目链接
https://www.dotcpp.com/oj/problem1583.html
题目大意
求a,b的乘积,a,b不超过10000位。
解题思路
高精度,毋庸置疑。
高精度乘法(高精度*高精度)
本质思想:
字符串输入 -> 逆置 -> 加减乘除 -> 判断最终长度 -> 逆向输出
大数通过输入字符串的方式输入,再把字符串转化成int数组,int数组每个位置放置的是大数的每一位。对int数组进行操作。
核心代码:
cin>>s1+1>>s2+1;//以字符串形式输入 len1=strlen(s1+1);//求出位数 len2=strlen(s2+1); for(int i=1;i<=len1;i++) a[len1-i+1]=s1[i]-'0';//将大数逆置,不明白可以看下面的解释 for(int i=1;i<=len2;i++) b[len2-i+1]=s2[i]-'0';//逆置 for(int i=1;i<=len1;i++) for(int j=1;j<=len2;j++){ c[i+j-1]+=a[i]*b[j];//非常巧妙,i+j-1正好是i和j相乘之后直接影响的位置。注意是+= c[i+j]+=c[i+j-1]/10;//先让i+j-1的高一位加上进位。注意是+= c[i+j-1]%=10;//计算本位的数 }
逆置:
为什么要逆置?
让低位在小索引位置,方便进位。你想想要是不逆置,直接每一位每一位操作,当操作到两数的最高位(即索引为0 或者 1)时,若发生进位,最高位索引为1还好说,要是为0,进位如何保存?而逆置之后,向高索引位置进位就十分容易了。
因此,我们可以写出代码了:
#include<bits/stdc++.h> using namespace std; const int N=10010; const int M=2*N; int c[M],a[N],b[N]; int len1,len2; char s1[N],s2[N]; int main(){ scanf("%s%s",s1+1,s2+1); //cin>>s1+1>>s2+1; len1=strlen(s1+1); len2=strlen(s2+1); for(int i=1;i<=len1;i++) a[len1-i+1]=s1[i]-'0'; for(int i=1;i<=len2;i++) b[len2-i+1]=s2[i]-'0'; for(int i=1;i<=len1;i++) for(int j=1;j<=len2;j++){ c[i+j-1]+=a[i]*b[j]; c[i+j]+=c[i+j-1]/10; c[i+j-1]%=10; } int len=len1+len2-1;//也是非常巧妙的地方,记住就ok,而且也不难理解,自己比划比划就知道了 if(c[len+1]!=0) len++;//判断一下,要是还有进位保留在len+1的位置,说明len+1位置也有数,就让len++ for(int i=len;i>=1;i--)//因为高索引是高位,输出的时候要先输出高位,因此先输出高索引。 printf("%d",c[i]);//cout<<c[i]; }
哎,你会发现并没法AC,这是因为数据规模的问题,当相乘的数都为1w位时,复杂度为1e8,不大行!
所以,引入进阶版高精度 乘以 高精度:
压位操作:
我们上面的a数组每一个元素是一位数,这样挺浪费的,所以,我们这次每一个元素存4位数。
a数组的长度变短了,所以循环的次数也就少了,时间复杂度也就低了。
压位核心代码:(这里得自己理解一下了)
for(int i=len1;i>=1;i--,tmp*=10)//tmp是10的某次幂 if((len1-i)%4==0) a[++alen]=s1[i]-'0',tmp=1; else a[alen]=(s1[i]-'0')*tmp+a[alen]; for(int i=len2;i>=1;i--,tmp*=10) if((len2-i)%4==0) b[++blen]=s2[i]-'0',tmp=1; else b[blen]=(s2[i]-'0')*tmp+b[blen];
AC代码
#include<bits/stdc++.h> using namespace std; const int N=10010; const int M=2*N; int c[M],a[N],b[N]; char s1[N],s2[N]; int alen,blen,tmp; int main(){ cin>>s1+1>>s2+1; int len1=strlen(s1+1); int len2=strlen(s2+1); for(int i=len1;i>=1;i--,tmp*=10) if((len1-i)%4==0) a[++alen]=s1[i]-'0',tmp=1; else a[alen]=(s1[i]-'0')*tmp+a[alen]; for(int i=len2;i>=1;i--,tmp*=10) if((len2-i)%4==0) b[++blen]=s2[i]-'0',tmp=1; else b[blen]=(s2[i]-'0')*tmp+b[blen]; for(int i=1;i<=alen;i++) for(int j=1;j<=blen;j++){ c[i+j-1]+=a[i]*b[j]; c[i+j]+=c[i+j-1]/10000;//除10000,不是除1000!!!debug好久 c[i+j-1]%=10000; } int len=alen+blen-1; if(c[len+1]!=0) len++; for(int i=1;i<=len;i++) if(i!=1) printf("%04d",c[len-i+1]);//重点!当遍历到非最高四位时,要保证输出4位,不足4位前面补零 else cout<<c[len];//最高四位,直接输出,不带0 }
特别注意
特别注意,当存在乘数为0的情况时,需要特判并输出0!!!!
我在这debug过好久!
压位一般压到四位,因为1e4 * 1e4 < 2147483647,但是开五位就超int了;
先手算一下压到四位时间复杂度会不会超,若还是超,可以压到七位,但是要开LL。
吐槽
网上搜高精度乘法压位,搜到的都是些什么东西啊,讲的不忍直视,要不就代码奇葩,可读性差(应该是我太菜了),所以才写了这篇。