【转】HDOJ 1042 解题报告
第一次写解题报告,仔细研究一道题的感觉很不错。呵
报告的内容:
1.计算大数乘法的朴素版本
“传说”中的x位一存是什么意思,怎么来的。
2.四位一存
3.九位一存
4.五位一存(仅适用此题的最快版本)
5.大数乘法模板(9位一存)
1. 一位一存,下面那些版本的根源,建议大家先把这个程序的过程理解明白,欲速则不达啊,呵呵~
运行结果见代码下面:
#include <stdio.h>
#include <string.h>
int main()
{
int a[40000],c[40000];//10000^10000也就40001位,10000!可以估计出是30000多位,所以开40000大小的数组
int b[10];
int n;
int i,j,k;
int l,la,lb,lc;
while(EOF != scanf("%d",&n))
{
if(n==0 || n==1) { printf("1\n"); continue;} //几种特殊情况
a[0] = 1;
la = 1; //注意之后要读取la
for(k=2; k<=n; k++)
{
l = k;
lb = 0;
for(j=0; l; j++) {b[j] = l%10; l /= 10; lb++;} //把整型数放入数组
memset(c,0,sizeof(c));
for(j=0; j<lb; j++) //模拟乘法运算的过程
for(i=0; i<la; i++)
{
c[j+i] += b[j]*a[i];
c[j+i+1] += c[j+i]/10;
c[j+i] %= 10;
lc = i+j+1; }//c数组的位数,假设每次都有进位
if(0 == c[lc]) lc--;
la = lc+1;
for(i=0; i<=lc ;i++)
a[i] = c[i];
}
for(i=lc; i>=0; i--)
printf("%d",a[i]);
printf("\n");
}
return 0;
}
Time Limit Exceeded 1042 5000MS 468K
可见,这样根本通不过,5000MS的时间都不够
2.4位一存
int数组里只存一个单位数,0~9,这显然是对int的一种浪费,而且似乎9999*9999并不比9*9慢多少?(见<<算法艺术与信息学竞赛>>),那么我们尝试每个a[i]里多存几位
我们知道int的范围是21亿多,如果a[i]里存的数位5位数,99999*99999将大于int可表示的范围,所以我们让a[i]存4位的整数,将避免溢出的问题.四位一存算法应运而生了~
#include <stdio.h>
#include <string.h>
#define MOD 10000
int main()
{
int a[10000],b[6],c[10000];//10000!大概有10000*4=40000位,4位一存,数组大小需要开40000/4=10000的大小
int i,j,k,n,l,la,lb,lc;
while(EOF !=scanf("%d",&n))
{
if(n==0||n==1) {printf("1\n");continue;} //几种特殊情况
for(a[0]=1, la=1, k=2; k<=n; k++)
{
for(l=k,lb=0,i=0; l; i++) { b[i] = l%MOD; l/=MOD;lb++;}
memset(c,0,sizeof(c));
for(j=0; j<lb; j++)
for(i=0; i<la; i++)
{
c[j+i] += b[j]*a[i];
c[j+i+1] += c[j+i]/MOD;
c[j+i] %= MOD;
}
la = la+lb; //两个数相乘,假设a有4位,b有2位,那么a*b可能是5位或者6位,先假设有6位
if(0 == c[la-1]) la--; //看一下有没有第六位,第六位即c[5]
for(i=0; i<la; i++) a[i] = c[i];
}
printf("%d",a[la-1]);
for(i=la-2; i>=0; i--)
printf("%04d",a[i]);
printf("\n");
}
return 0;
}
Accepted 1042 562MS 260K
由截图知,速度果然大大提升,原来的TLE,现在500MS就通过了
3.9位一存:
由4位一存的结果我们可以继续想,继续每个a[i]多存几位真的可以变快,那再多存几位,应用__int64这个类型,我们存9位如何?
结果见代码后面.
#include <stdio.h>
#define MOD 1000000000
#define LEN 4000
/*计算 n!*/
int mut(int r[], int n)
{
int i, j, p=1;
__int64 t = 0;
r[0] = 1;
for(i=2; i<=n; i++)
{
/*对r中每个可用元素依次和i相乘*/
for(j=0; j<p; j++) //注意:这里面的3句话和上面的代码不一样,自己重新推敲一下
{ /*并且由于9位一存,b数组不再有存在的必要,每次都只用到b[0]而已,进而导致c数组也没有存在的必要*/
t = (__int64)r[j] * i + t;/*防止r[j]*i结果被int32_t截断,先转化为int64再计算*/
r[j] = t % MOD ;
t = t / MOD;
}
/*一轮乘法过后,是否产生新的进位*/
if (t > 0)
{
r[p++] = t;
t = 0;
}
}
return p;
}
/*输出*/
void print(int r[], int p)
{
for(printf("%d",r[--p]); p--;)
{
printf("%09d", r[p]);/*中间数字补0*/
}
puts("");
}
int main()
{
int r[LEN], n, p;/*大整数数组,当前输入的n,数组中已使用的元素个数*/
while(scanf("%d",&n)!=EOF)
{
p = mut(r, n);
print(r, p);
}
return 0;
}
Accepted 1042 531MS 192K
结果表明,速度几乎没有提升.为什么?因为32机本来就不支持64位的数,肯定是在处理__int64数据类型的时候耗费太多的时间(具体可以自己查看汇编代码),那么还能更快吗?
4.a[i]里存的位数要尽量多,还要避免__int64的使用,结合题意,我们可以5位一存,因为相乘的两个数一个最大是10000,另一个即使是99999也不超过int可表示的最大类型.数组b和c这里也同样不再需要.
#include <stdio.h>
#include <string.h>
//#include <stdlib.h>
//#include <time.h>
#define MOD 100000
int main()
{
// clock_t start,finish;
// double duration;
int a[8000];//10000!大概有10000*4=40000位,5位一存,数组大小需要开40000/5=8000的大小
int i,k,n,t,la;
while(EOF !=scanf("%d",&n))
{
// start = clock();
if(n==0||n==1) {printf("1\n");continue;} //特殊情况
a[0] = 1;
la = 1;
t=0;
for(k=2; k<=n; k++)
{
for(i=0; i<la; i++)
{
t += k * a[i];
a[i] = t%MOD;
t = t/MOD;
}
if (t > 0) //一轮乘法过后,是否产生新的进位
{
a[la++] = t;
t = 0;
}
}
// finish = clock();
printf("%d",a[la-1]);
for(i=la-2; i>=0; i--)
printf("%05d",a[i]);
printf("\n");
// duration = (double)(finish-start)/CLOCKS_PER_SEC;
// printf("%lf\n",duration);
}
return 0;
}
Accepted 1042 296MS 212K
果然,速度再次有了不小的提升.
5.大数乘法模板
1)4位一存
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define LEN 10000
#define MOD 10000
int main()
{
clock_t start,finish;
double duration;
char s1[LEN],s2[LEN];
int a[LEN],b[LEN],c[2*LEN];//此模板使用范围,相乘的两个数每个数小于40000位,可容纳80000以内的结果
int i,j,la,lb,lc,pos;
while(EOF !=scanf("%s%s",s1,s2))
{
start = clock();
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
for(la=0,pos=strlen(s1)-4; pos>=0; pos-=4)
a[la++] = (s1[pos]-48)*1000 + (s1[pos+1]-48)*100 + (s1[pos+2]-48)*10 + (s1[pos+3]-48);
pos += 4;
for(j=0; j<pos; j++)
a[la] = a[la]*10 + (s1[j]-48);
if(j>0)
la++;
for(lb=0,pos=strlen(s2)-4; pos>=0; pos-=4)
b[lb++] = (s2[pos]-48)*1000 + (s2[pos+1]-48)*100 + (s2[pos+2]-48)*10 + (s2[pos+3]-48);
pos += 4;
for(j=0; j<pos; j++)
b[lb] = b[lb]*10 + (s2[j]-48);
if(j>0)
lb++;
for(j=0; j<lb; j++)
for(i=0; i<la; i++)
{
c[j+i] += b[j]*a[i];
c[j+i+1] += c[j+i]/MOD;
c[j+i] %= MOD;
}
lc = la + lb;
if(0 == c[lc-1])
lc--;
finish = clock();
printf("%d",c[lc-1]);
for(i=lc-2; i>=0; i--)
printf("%04d",c[i]);
printf("\n");
duration = (double)(finish-start)/CLOCKS_PER_SEC;
printf("%lf\n",duration);
}
return 0;
}
2)九位一存
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define LEN 10000
#define MOD 1000000000
int main()
{
clock_t start,finish;
double duration;
char s1[LEN],s2[LEN];
int a[LEN],b[LEN];
__int64 c[2*LEN];//此模板使用范围,相乘的两个数每个数小于90000位,可容纳180000以内的结果
int i,j,la,lb,lc,pos;
while(EOF !=scanf("%s%s",s1,s2))
{
start = clock();
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
for(la=0,pos=strlen(s1)-9; pos>=0; pos-=9)
a[la++] = (s1[pos]-48)*100000000 + (s1[pos+1]-48)*10000000 +
(s1[pos+2]-48)*1000000 + (s1[pos+3]-48)*100000 + (s1[pos+4]-48)*10000 +
(s1[pos+5]-48)*1000 + (s1[pos+6]-48)*100 + (s1[pos+7]-48)*10 +
(s1[pos+8]-48);
for(pos += 9,j=0; j<pos; j++)
a[la] = a[la]*10 + (s1[j]-48);
if(j!=0) la++;
for(lb=0,pos=strlen(s2)-9; pos>=0; pos-=9)
b[lb++] = (s2[pos]-48)*100000000 + (s2[pos+1]-48)*10000000 +
(s2[pos+2]-48)*1000000 + (s2[pos+3]-48)*100000 + (s2[pos+4]-48)*10000 +
(s2[pos+5]-48)*1000 + (s2[pos+6]-48)*100 + (s2[pos+7]-48)*10 +
(s2[pos+8]-48);
for(pos += 9,j=0; j<pos; j++)
b[lb] = b[lb]*10 + (s2[j]-48);
if(j!=0) lb++;
for(j=0; j<lb; j++)
for(i=0; i<la; i++)
{
c[j+i] += (__int64)b[j]*a[i];
c[j+i+1] += c[j+i]/MOD;
c[j+i] %= MOD;
}
lc = la + lb;
if(0 == c[lc-1])
lc--;
finish = clock();
printf("%I64d",c[lc-1]);
for(i=lc-2; i>=0; i--)
printf("%09I64d",c[i]);
printf("\n");
duration = (double)(finish-start)/CLOCKS_PER_SEC;
printf("%lf\n",duration);
}
return 0;
}
结果发现在计算a*a(a为9999...99999,1W个9)的时候,这两个版本都是0.016秒,似乎还是难分伯仲~~
但是比普通的大数乘法模板的确快很多,普通的已经需要17秒多