问题描述
本文主要给出大数乘法的一般思路。说明大数乘法的两种思路,并且结合两道题目给出对于大数和常规整数乘法代码优化的必要性。
思路
基本来说,还是大整数的那套思路。
要进行处理的数字,超过了计算机语言所能提供类型的最大范围。只能自己写数组存储每一位数字。由于不是内置类型,所以没有相应操作的支持。只能自己写,人工模拟减法操作。
但是对于大数乘法这一块,自己在看了点网上的资料之后,以为自己明白了大数乘法的思路,可以做题了就没有进一步思考。直到这两天重新看的时候才发现了之前没有想到的事情。
我们平常所说的大数乘法是大数和常规整数的乘法,这一点是我之前没有仔细考虑的。其实也是显而易见的事情。但是他的算法并不是严格按照按位操作去实现的。所以,当我们说大数乘法的时候,应该是两种。一种是大数和普通整数的乘法,一种是大数和大数的乘法。当然前者也可以完全用后者来实现,只不过对于前者而言,有更简单的办法。
大数和普通整数的乘法
算法思路:被乘数的每一位乘以乘数,然后得到相应位的值以及进位。这就要求被乘数是大数,而乘数则不能是大数。也是也人工模拟的办法,按位去进行计算,只不过但是对于乘数而言,它的计算并不是按位去计算的。
但是我们知道,传统的乘法计算,被乘数和乘数都是按位去进行计算的。
先看一道简单的题目: [ jobdu-1067 ]
题目描述:
输入一个整数n,输出n的阶乘
输入:
一个整数n(1<=n<=20)
输出:
n的阶乘
样例输入:
3
样例输出:
6
考虑到n的最大值就是20,所以数组的大小分配1024没有问题。
代码如下:
/* input: n-乘数 process: 1.data初始化为1,小端机存储 2.循环:2 - n次,每一次循环应该做的是用大数的每一位乘以 乘数,获取当前位的值和进位 2.1.循环:遍历大数的每一位, c = 0 2.1.1.用当前位的值 * i 2.1.2.计算当前位的值 2.1.3.计算进位 3.忽略前导0 4.按位输出 output: */
#include <iostream>
#include <cstring>
#include <fstream>
//#define LOCAL
const int maxn = 1024;
int data[ maxn ];
void bign_mul( int n );
int main()
{
#ifdef LOCAL
std::ifstream cin;
cin.open("input.dat");
#endif
int n = 0;
while( std::cin >> n )
{
bign_mul(n);
}
#ifdef LOCAL
cin.close();
#endif
return 0;
}
void bign_mul( int n )
{
memset( data, 0, sizeof(data) );
data[0] = 1;
for( int i = 2; i <= n; ++i )
{
int c = 0;
for( int j = 0; j < maxn; ++j )
{
int tmp = data[j] * i + c ;
data[j] = tmp % 10;
c = tmp / 10;
}
}
int i = 0;
for( i = maxn - 1; !data[i]; --i );
for( int j = i; j >= 0; --j )
std::cout << data[j];
std::cout << std::endl;
}
我们下面再看一道题目: [ jobdu-1076 ]
题目描述:
输入一个正整数N,输出N的阶乘。
输入:
正整数N(0<=N<=1000)
输出:
输入可能包括多组数据,对于每一组输入数据,输出N的阶乘
样例输入:
4
5
15
样例输出:
24
120
1307674368000
分析:还是同一个问题,只不过范围变了下。现在阶乘最大值可以取到1000。对于上面的代码。只需修改下maxn即可。
但是上面的代码,无论如何都是过不了的。因为这个题目最小的maxn是2566,但是上面的代码在maxn=2562的时候就已经TLE。说明上面的代码在时间上面需要优化。至于上面的2566和2562这两个值是怎么得到的,就是不断试出来的。
其实这个题做的时候也比较幸运,因为我直接改了优化的版本去做的。前两次一直WA,因为数组空间开辟较小,只获得了部分数据值。将数组空间改大即可。优化的思路也很简单,每次做乘法的时候不要枚举data数组的所有位,因为不是所有位是有效位。所以增加一个控制有效位长度的变量即可。
代码如下:
#include <iostream>
#include <cstring>
#include <fstream>
//#define LOCAL
const int maxn = 10000;
int data[maxn];
void bign_mul( int n );
int main()
{
#ifdef LOCAL
std::ifstream cin;
cin.open( "input.dat" );
#endif
int n = 0;
while( std::cin >> n )
{
bign_mul(n);
}
#ifdef LOCAL
cin.close();
#endif
return 0;
}
void bign_mul( int n )
{
std::memset( data, 0, sizeof(data) );
data[0] = 1;
int len = 1; // 有效位长度
for( int i = 2; i <= n; ++i )
{
int c = 0;
for( int j = 0; j < maxn && ( j < len || c ) ; ++j )
{
int tmp = data[j] * i + c;
data[j] = tmp % 10;
c = tmp / 10;
if( j >= len )
++len;
}
}
for( int i = len - 1; i >= 0; --i )
std::cout << data[i];
std::cout << std::endl;
}
总结
其实在做acm题的时候给我比较深的感觉就是对于程序鲁棒性的要求,也就是你的程序仅仅是”看起来正确”是远远不够的,必须要做到100%的正确才行。