和吴昊一起玩推理 Round 13 —— 各种大数各种调

 各种调”是什么意思呢?这里有必要提一个妹纸。这张图出自我大三的一次数模校赛,是用EVIEWS拟合生成的。我们利用VAR向量自回归模型来拟合房价 与人们的年平均收入的关系。这里有很多性质量,比如表上的F-特征值啊,均方根啊等等。那么,什么叫“各种调”呢?因为数学建模的国赛和美赛评定标准是不 一样的,美赛是你必须写出你自己的东西,不能有哪怕是一行的抄袭,强调一种独创性,而国赛则是强调一种内容的“丰富性”,至于你怎么抄,无所谓啦!所以, 离比赛前一天晚上,那位妹纸告诉我“模型的数据已经各种调了,但是,还是存在诸多的问题”。

 (妹纸的照片略去,否则她会杀了我)

 

 这 里,我以“各种大数各种调”为题来阐述在吴昊品游戏核心算法Round 12中我所说的“大整数问题”。所谓的大整数,是计算机中的一个变量类型的概念,指的是用(通常在C/C++)long long int类型仍然装载不下的一个数据,比如100位的整数,这样的数叫做大整数。在JAVA中,已经有专门的数据类型可以装填了,那就是 BigInteger,可以装载各种变态的大整数,但是在C/C++中只能用字符串来模拟了。在ACM-ICPC的模板中,这样的大整数问题往往可以利用 现成的大整数模板来解决。

 大整数加法:

 (Source:POJ 1503,读入两个大整数,输出一个大整数)

 

  1  #include <stdio.h>
  2  #include <string.h>
  3  #include <stdlib.h>
  4 
  5  #define MAX_LEN 200
  6  
  7  //这里将两个大整数进行扭转
  8  int an1[MAX_LEN+10];
  9  int an2[MAX_LEN+10];
 10  
 11  //存放读入的两个大整数
 12  char szLine1[MAX_LEN+10];
 13  char szLine2[MAX_LEN+10];
 14 
 15  int main()
 16  {
 17    //存储两个大整数
 18    scanf("%s", szLine1);
 19    scanf("%s", szLine2);
 20    int i, j;
 21     
 22    //库函数memeset将地址an1开始的sizeof(an1)字节内容置成0
 23    //sizeof(an1)的值就是an1的长度
 24 
 25    memset( an1, 0sizeof(an1));       
 26    memset( an2, 0sizeof(an2));
 27    
 28    //下面将szLine1中存储的字符串形式的整数转换到an1中去,
 29    //an1[0]对应于个位
 30    int nLen1 = strlen( szLine1);
 31    j = 0;
 32    //这里将这个大整数的数位扭转
 33    for( i = nLen1 - 1;i >= 0 ; i --)
 34      an1[j++] = szLine1[i] - '0';    
 35    int nLen2 = strlen(szLine2);
 36    j = 0;
 37    //这里同理,对第二个大整数的数位进行扭转
 38    for( i = nLen2 - 1;i >= 0 ; i--)
 39      an2[j++] = szLine2[i] - '0';
 40 
 41   //点睛之处,考虑到进位功能的实现
 42    for( i = 0;i < MAX_LEN ; i ++ )
 43    {
 44      an1[i] += an2[i];//逐位相加
 45      if( an1[i] >= 10 )//看是否要进位 
 46      {        
 47        an1[i] -= 10;
 48        an1[i+1]++;//模拟进位
 49      }
 50    }
 51    bool bStartOutput = false;//此变量用于跳过多余的0
 52    for( i = MAX_LEN; i >= 0; i-- )
 53    {
 54      if( bStartOutput)    
 55        printf("%d", an1[i]);  //如果多余的0已经都跳过,则输出     
 56      else if( an1[i] )
 57      {           
 58        printf("%d", an1[i]);
 59        bStartOutput = true//碰到第一个非0的值,就说明多余的0已经都跳过                  
 60      }
 61    }
 62    //这里说明所有的数位都为0
 63    if(!bStartOutput)//结果为0特殊处理
 64      printf("0");
 65    //这里起到一个暂停的功能,来让读者看清眼前的数字,不然计算机就只会闪过了
 66    system("pause");
 67    return 0;
 68  }
 69 
 70  大整数减法:
 71 
 72  (Source:POJ 2736,读入两个大整数,输出一个大整数)
 73 
 74    #include<stdio.h>
 75  #include<string.h>
 76  #include<iostream>
 77  using namespace std;
 78  
 79  //假设数位为100位左右,且不可能超过100位
 80  #define N 110
 81  
 82  //读入两大整数
 83  char str1[N],str2[N];
 84  
 85  //这些数组提供如"大整数加法"这样的扭转支持
 86  int a[N],b[N],c[N];
 87  
 88  int main()
 89  {
 90    int n,i,j;
 91    int len1,len2;
 92    //这里读入若干个样例
 93    cin>>n;
 94    while(n--)
 95    {
 96      //初始化,准备进行"扭转"的数组
 97      memset(a,0,sizeof(a));
 98      memset(b,0,sizeof(b));
 99      memset(c,0,sizeof(c));
100      //用输入流读入两个大整数
101      cin>>str1>>str2;
102      //获取大整数的长度
103      len1=strlen(str1);
104      len2=strlen(str2);
105      //将两个大整数"首尾逆置"
106      j=0;
107      for(i=len1-1;i>=0;i--)
108        a[j++]=str1[i]-'0';
109      j=0;
110      for(i=len2-1;i>=0;i--)
111        b[j++]=str2[i]-'0';
112      //借位处理,点睛部分
113      for(i=0;i<len1;i++)
114      {
115        c[i]+=(a[i]-b[i]);
116        if(c[i]<0)
117        {
118          c[i+1]--;
119          c[i]+=10;
120        }
121      }
122      //判断最后一位的借位情况
123      i=len1-1;
124      //去掉所有的首位0
125      while(!c[i])
126      {
127        i--;
128        if(i==-1break;
129      }
130      //如果全是0,则输出0
131      if(i==-1)
132      {
133        cout<<"0\n";
134        getchar();
135        //这里为什么是continue而不是break呢?因为有多个样例,不应该马上就整个退出,或者这里不写也可以,加continue是为了代码的工整性
136        continue;
137      }
138      //如果不是所有位数都为0,则输出正确的结果,注意要从i=len1-1输出到i=0
139      while(i>=0)
140      {
141        cout<<c[i];
142        i--;
143      }
144      cout<<endl;
145      getchar();
146    }
147    return 0;
148  }

 

 大整数减法:

 (Source:POJ 2736,读入两个大整数,输出一个大整数)

 

 1  #include<stdio.h>
 2  #include<string.h>
 3  #include<iostream>
 4  using namespace std;
 5  
 6  //假设数位为100位左右,且不可能超过100位
 7  #define N 110
 8  
 9  //读入两大整数
10  char str1[N],str2[N];
11  
12  //这些数组提供如"大整数加法"这样的扭转支持
13  int a[N],b[N],c[N];
14  
15  int main()
16  {
17    int n,i,j;
18    int len1,len2;
19    //这里读入若干个样例
20    cin>>n;
21    while(n--)
22    {
23      //初始化,准备进行"扭转"的数组
24      memset(a,0,sizeof(a));
25      memset(b,0,sizeof(b));
26      memset(c,0,sizeof(c));
27      //用输入流读入两个大整数
28      cin>>str1>>str2;
29      //获取大整数的长度
30      len1=strlen(str1);
31      len2=strlen(str2);
32      //将两个大整数"首尾逆置"
33      j=0;
34      for(i=len1-1;i>=0;i--)
35        a[j++]=str1[i]-'0';
36      j=0;
37      for(i=len2-1;i>=0;i--)
38        b[j++]=str2[i]-'0';
39      //借位处理,点睛部分
40      for(i=0;i<len1;i++)
41      {
42        c[i]+=(a[i]-b[i]);
43        if(c[i]<0)
44        {
45          c[i+1]--;
46          c[i]+=10;
47        }
48      }
49      //判断最后一位的借位情况
50      i=len1-1;
51      //去掉所有的首位0
52      while(!c[i])
53      {
54        i--;
55        if(i==-1break;
56      }
57      //如果全是0,则输出0
58      if(i==-1)
59      {
60        cout<<"0\n";
61        getchar();
62        //这里为什么是continue而不是break呢?因为有多个样例,不应该马上就整个退出,或者这里不写也可以,加continue是为了代码的工整性
63        continue;
64      }
65      //如果不是所有位数都为0,则输出正确的结果,注意要从i=len1-1输出到i=0
66      while(i>=0)
67      {
68        cout<<c[i];
69        i--;
70      }
71      cout<<endl;
72      getchar();
73    }
74    return 0;
75  }

大整数乘法:(这个是Round 12的火柴游戏中没有的,这里补上)

 (Source:POJ 2980,读入两个大整数,输出一个大整数)

 我们先看一个例子,就是835*49:

 

 由此,我们可以总结出一个规律性的东西:即一个数的第i位和另一个数的第j位相乘所得的数,一定是要累加到结果的第i+j位上。这里的i,j都是从右往左,从0开始的数。

 

 1  #include<stdio.h>
 2  #include<string.h>
 3  
 4  //这里存储两个100位数的乘积所能得到的最大位,这里考虑的特殊情况为每位都是9的情况
 5  #define max 200
 6 
 7  int main()
 8  {
 9    char s[max+1];
10    int i,j,s1,s2,n1[max],n2[max],n[2*max];
11    
12    //这里也是一种逆序存储吧
13    memset(n1,0,sizeof(n1));
14    memset(n2,0,sizeof(n2));
15    memset(n,0,sizeof(n));
16    
17    scanf("%s",s);
18    s1=strlen(s);
19    
20    //将输入的数读入整型数组
21    for(i=s1-1,j=0;i>=0;i--,j++)
22    n1[j]=s[i]-'0';
23    
24    //这一步同理
25    scanf("%s",s);
26    s2=strlen(s);
27    for(i=s2-1,j=0;i>=0;i--,j++)
28      n2[j]=s[i]-'0';
29    
30    //点睛之笔,其规律已经在上图分析过了
31    for(i=0;i<s1;i++)
32    {
33      for(j=0;j<s2;j++)
34      {
35        n[i+j]+=n1[i]*n2[j];
36      }
37    }
38    
39    //这里考虑进位的问题
40    for(i=0;i<s1+s2-1;i++)
41    {
42      if(n[i]>=10)
43      {
44        n[i+1]+=n[i]/10;
45        n[i]=n[i];
46      }
47    }
48    
49    //去除首位的0
50    for(i=s1+s2;n[i]==0&&i>=0;i--);
51    
52    if(i>=0)
53    {
54      for(;i>=0;i--)
55        printf("%d",n[i]);
56    }
57    //这里是考虑的特殊情况,就是每一位都是0
58    else
59    {
60      printf("0");
61    }
62    return 0;
63  }

 大整数除法:

 (Source:POJ 2737,读入两个大整数,输出一个大整数)

 从这里可以看出,大整数加法是大整数乘法的基础,而大整数减法是大整数除法的基础。

 思想是反复做减法,看看从被除数里最多能减去多少个除数,商就是多少。一个
一个减显然太慢,如何减得更快一些呢?以 7546 除以 23 为例来看一下:开始商为 0。先减去 23 的 100 倍,就是 2300,发现够减 3 次,余下 646。于是商的值就增加 300。然后用 646减去 230,发现够减 2 次,余下 186,于是商的值增加 20。最后用 186 减去 23,够减 8 次,因此最终商就是 328。
  
所以本题的核心是要写一个大整数的减法函数,然后反复调用该函数进行减法操作。计算除数的 10 倍、100 倍的时候,不用做乘法,直接在除数后面补 0 即可。 

 

  1 #include<string.h>
  2  #include<stdio.h>
  3  
  4  //考虑到两个大整数相除,最终的商只会是越来越小的
  5  #define max 100
  6 
  7  int sub(int * p1,int * p2,int len1,int len2)
  8  {
  9    int i,flag=0;
 10    //被减数不可能比除数要小
 11    if(len1<len2)
 12      return -1;
 13    //如果被减数和减数的数位相等的话,我们考虑哪个大的时候需要添加一个flag变量做标记,应该这里比较的时候需要每一位每一位地比对
 14    else if(len1==len2)
 15    {
 16      for(i=len1-1;i>=0;i--)
 17      {
 18        if(p1[i]>p2[i])
 19          flag=1;
 20        else if(p1[i]<p2[i])
 21        {
 22          if(flag==0)
 23            return -1;
 24        }
 25      }
 26    }
 27    //考虑减数的借位处理
 28    for(i=0;i<len1;i++)
 29    {
 30      p1[i]-=p2[i];
 31      if(p1[i]<0)
 32      {
 33        p1[i]+=10;
 34        p1[i+1]--;
 35      }
 36    }
 37    //排除起始位的0的情况
 38    for(i=len1-1;i>=0;i--)
 39    {
 40      if(p1[i])
 41        return i+1;
 42    }
 43    return 0;
 44  }
 45 
 46  int main()
 47  {
 48    int t,i,k,j,size,s1,s2,times,temp,flag;
 49    
 50    //这里n1和n2分别是除数和被除数,而result数组装载结果
 51    int n1[max];
 52    int n2[max];
 53    int result[max];
 54    
 55    //读入输入的数
 56    char n[max+1];
 57    
 58    //读入样例的数目
 59    scanf("%d",&t);
 60    
 61    for(i=0;i<t;i++)
 62    {
 63      memset(n1,0,sizeof(n1));
 64      memset(n2,0,sizeof(n2));
 65      memset(result,0,sizeof(result));
 66      scanf("%s",n);
 67      s1=strlen(n);
 68      size=s1;
 69      //逆序存入被除数
 70      for(j=0,k=s1-1;k>=0;j++,k--)
 71      {
 72        n1[j]=n[k]-'0';
 73      }
 74      scanf("%s",n);
 75      s2=strlen(n);
 76      //逆序存入除数
 77      for(j=0,k=s2-1;k>=0;j++,k--)
 78      {
 79        n2[j]=n[k]-'0';
 80      }
 81      s1=sub(n1,n2,s1,s2);
 82      //如果被除数比除数要小的话,直接输出0即可
 83      if(s1<0)
 84      {
 85        printf("0\n");
 86        continue;
 87      }
 88      //这里考虑一种除完了之后商全为0的情况,与前面一种情况是不相同的
 89      if(s1==0)
 90      {
 91        printf("0\n");
 92        continue;
 93      }
 94 
 95         //减掉一次之后,商应该自增一个
 96      result[0]++;
 97      times=s1-s2;
 98      //如果在减掉一次之后已经不够再减下去了,则输出,利用goto跳转到output那边
 99      if(times<0)
100        goto output;
101 
102        //将n2乘以10的某次幂,使得结果长度和n1相同。
103      for(j=s1;j>=0;j--)
104      {
105        if(j>=times)
106          n2[j]=n2[j-times];
107        else
108          n2[j]=0;
109      }
110      s2=s1;
111      for(j=0;j<=times;j++)
112      {
113 
114            //一直减到不够减为止,先减去若干个 n2×(10 的 times 次方),
115        //不够减了,再减去若干个  n2×(10  的  nTimes-1  次方),......
116 
117          while((temp=sub(n1,n2+j,s1,s2-j))>=0)
118        {
119          s1=temp;
120 
121          //每成功减一次,则将商的相应位加 1
122          result[times-j]++;
123        }
124      }
125    output:
126      //考虑进位的问题
127      for(j=0;j<size;j++)
128      {
129        if(result[j]>=10)
130        {
131          result[j+1]+=(result[j]/10);
132          result[j]=result[j];
133        }
134      }
135      //这里仍然是标记一个flag,将在前面的一些0都刷去
136      flag=0;
137      for(j=size-1;j>=0;j--)
138      {
139        if(flag)
140        {
141          printf("%d",result[j]);
142        }
143        else if(result[j])
144        {
145          printf("%d",result[j]);
146          flag=1;
147        }
148      }
149      if(!flag)
150        printf("0");
151      printf("\n");
152    }
153    return 0;
154  }

 


 

posted on 2013-02-28 12:54  吴昊系列  阅读(212)  评论(0)    收藏  举报

导航