和吴昊一起玩推理 Round 13 —— 各种大数各种调
“各种调”是什么意思呢?这里有必要提一个妹纸。这张图出自我大三的一次数模校赛,是用EVIEWS拟合生成的。我们利用VAR向量自回归模型来拟合房价 与人们的年平均收入的关系。这里有很多性质量,比如表上的F-特征值啊,均方根啊等等。那么,什么叫“各种调”呢?因为数学建模的国赛和美赛评定标准是不 一样的,美赛是你必须写出你自己的东西,不能有哪怕是一行的抄袭,强调一种独创性,而国赛则是强调一种内容的“丰富性”,至于你怎么抄,无所谓啦!所以, 离比赛前一天晚上,那位妹纸告诉我“模型的数据已经各种调了,但是,还是存在诸多的问题”。
(妹纸的照片略去,否则她会杀了我)
这 里,我以“各种大数各种调”为题来阐述在吴昊品游戏核心算法Round 12中我所说的“大整数问题”。所谓的大整数,是计算机中的一个变量类型的概念,指的是用(通常在C/C++)long long int类型仍然装载不下的一个数据,比如100位的整数,这样的数叫做大整数。在JAVA中,已经有专门的数据类型可以装填了,那就是 BigInteger,可以装载各种变态的大整数,但是在C/C++中只能用字符串来模拟了。在ACM-ICPC的模板中,这样的大整数问题往往可以利用 现成的大整数模板来解决。
大整数加法:
(Source:POJ 1503,读入两个大整数,输出一个大整数)
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, 0, sizeof(an1));
26 memset( an2, 0, sizeof(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==-1) break;
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,读入两个大整数,输出一个大整数)
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==-1) break;
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开始的数。
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 即可。
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 }
浙公网安备 33010602011771号