【转】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秒多

posted @ 2013-05-09 15:02  Geekers  阅读(234)  评论(0编辑  收藏  举报