《算法笔记》中,对于大整数的运算,给出了最基础的几个算法模板,包括大整数的结构体、输出、与字符串的转换,以及四则预算(不包含高精度与高精度的乘除)。但是在codeup训练当中,又出现了两种经典问题,第一是对大浮点数的处理,第二是对于大整数的进制转换问题,下面对于大整数的进制转换问题,说一种万能超级解法。
codeup相关题型对应标号为:(看完文章就可以试试)直接在csdn搜也可以搜到原题
对于进制转换问题(把M进制的a转换为N进制的b),如果手算,一个很简单的方法就是先把M进制的形式转换为10进制,然后再通过短除法得到N进制。
但如果对除法的过程比较熟悉,就会发现M到N实际可以直接转换,首先来看大整数运算当中除法函数的函数实现:
bign divide(bign a,int b,int &r)
{
bign c;
c.len=a.len;
for(int i=a.len-1;i>=0;i--)
{
r=r*10+a.d[i];
c.d[i]=r/b;
r=r%b;
}
while(c.len>=2&&c.d[c.len-1]==0)
{
c.len--;
}
return c;
}
在这个函数里面,最核心的部分很显然就在于:
r=r*10+a.d[i]; 1
c.d[i]=r/b; 2
r=r%b; 3
对于步骤2和步骤3中分别除以和取余的这个整数b,就是整个算式的除数,也是当我们利用短除法实现进制转换时的那个除数,也是我们的目标进制N,再看步骤1的r*10的这个10,为什么在这里面,是10呢?是因为我们的这个函数divide默认被除数是10进制的,所以每一位的权值是下一低位的10倍,之所以权值是10倍,不就是因为原来这个数是10进制吗?如果是M进制,那不就是乘以M吗?所以就可以得到一个M进制短除得到N进制的一个代码:
r=r*M+a.d[i];
c.d[i]=r/N;
r=r%N;
现在来考虑:上述这三行代码怎么去用,用在哪里?
很显然,用到这个代码,需要满足两个条件:
- 一个M进制的数需要把每一位保存在数组当中,因为如果是就放在字符数组里面,计算的时候还要不停换算,很麻烦
- M进制的数的每一位放在数组之后,每一位都是一个数,就是说当M>10时,这个数就会有字母表示更高的权值,我们只需在转换函数change中去稍作修改:
bign change(char a[]) { bign t; for(int i=strlen(a)-1; i>=0; i--) { if(a[i]>='0'&&a[i]<='9') t.d[t.len++] = a[i]-'0'; else t.d[t.len++] = (a[i]-'A'+10); } return t; }
这样就能把2--36进制的数的每位以整数的形式保存在数组当中。
有了上面的基础,就可以直接用一次“特殊的”短除法得到进制转换后的结果,通过循环,把每一位保存在字符数组当中,如果有必要,可以写一个颠倒函数reverse,把生成的数颠倒,才是最终正确的顺序,现在以codeup problem b 1950进制转换为例,说明做法:
while(scanf("%d%d",&m,&n)!=EOF) { getchar();//消去换行!!!!! char x[10001]; scanf("%s",x); bign a; a = change(x); char ans[1001]; int len = strlen(x),index=0; while(a.len>0)//a.len==0即辗转相除到了最后 { int carry = 0; for(int i=a.len-1; i>=0; i--) { int temp = a.d[i] + carry*m; carry = temp % n; a.d[i] = temp/n; } while(a.d[a.len-1]==0) a.len--; if(carry>=0&&carry<=9) ans[index++] = carry+'0'; else ans[index++] = carry-10+'a'; } for(int i=index-1; i>=0; i--) printf("%c", ans[i]); printf("\n"); }
注意是怎么去判断短除的末尾的,即当被除数长度变为0时。
那能否把这个循环短除过程封装到函数里面呢?
我们可以尝试写这么一个函数:
void scale_trans(bign a,char b[],int now,int to)
解释参数意思:
a表示准备被转换的大整数
b表示a转换后的大整数(以字符数组存放,在函数中也可以直接实现把顺序颠倒的功能)
now:a的进制
to:b的进制
直接给出代码:
void scale_trans(bign a,char b[],int now,int to) { memset(b,0,sizeof(b)); int index=0; while(a.len>0)//a.len==0即辗转相除到了最后 { int carry = 0; for(int i=a.len-1; i>=0; i--) { int temp = a.d[i] + carry*now; carry = temp % to; a.d[i] = temp/to; } while(a.d[a.len-1]==0) a.len--; if(carry>=0&&carry<=9) b[index++] = carry+'0'; else b[index++] = carry-10+'a'; } char temp[index]; for(int i=0;i<index;i++) temp[i]=b[index-1-i]; for(int i=0;i<index;i++) b[i]=temp[i]; }
有了这么多的基础,解决codeup problem 1952 f 10进制vs 2进制,跟喝凉水一样简单:
先看题:
10进制直接先转换为2进制,然后把上述scale_trans中最后一点的数组颠倒去掉,为什么要求掉?因为题中就是要的倒序2进制再转换为10进制,所以直接调用两次scale_trans就解决了:
#include<stdio.h> #include<string.h> struct bign{ int d[4000]; int len; bign(){ len = 0; memset(d, 0, sizeof(d)); } }; bign change(char a[]) { bign t; for(int i=strlen(a)-1; i>=0; i--) { if(a[i]>='0'&&a[i]<='9') t.d[t.len++] = a[i]-'0'; else t.d[t.len++] = (a[i]-'A'+10); } return t; } void scale_trans(bign a,char b[],int now,int to) { memset(b,0,sizeof(b)); int index=0; while(a.len>0)//a.len==0即辗转相除到了最后 { int carry = 0; for(int i=a.len-1; i>=0; i--) { int temp = a.d[i] + carry*now; carry = temp % to; a.d[i] = temp/to; } while(a.d[a.len-1]==0) a.len--; if(carry>=0&&carry<=9) b[index++] = carry+'0'; else b[index++] = carry-10+'a'; } b[index]='\0';//一定注意加0字符,否则会输出超限!!!!! } int main() { bign a; char num[4000]; while(scanf("%s",num)!=EOF) { char ans[4000]; a=change(num); scale_trans(a,ans,10,2); a=change(ans); scale_trans(a,ans,2,10); for(int i=0;i<strlen(ans);i++) printf("%c",ans[strlen(ans)-i-1]); printf("\n"); } return 0; }
完工