///////////////////////////////////////////////////////////////////////////////////////////////////////
作者:tt2767
声明:本文遵循以下协议自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0
查看本文更新与讨论请点击:http://blog.csdn.net/tt2767/article/details/45420067
链接被删请百度: CSDN t2767
///////////////////////////////////////////////////////////////////////////////////////////////////////
本模板的代码中,大数加,减,乘,除,求幂以及阶乘已经通过各大oj,取模算法暂时没有做到大数运算的题,但本地测试正确。
如果你还不太明白大数运算,建议你拿一张白纸边看边试一下,并且本方法默认大数字符串初始化为‘\0’。
一. 大数加法的原理
想象一下我们手算加法的时候怎么算?比如说678 + 78 ,开始算的时候是第一个数的个位 + 第二个数的个位,也就是8+8=16,结果中的个位是6,而1要进位进到十位上去,我们可以设一个变量carry来代表进位的值,由于单个数相加的最大值为9+9=18 所以carry的只能是1或0 。再向下看,个位算完了应该算十位了,由于个位有进位,所以计算十位时是carry+7+7 == 1+7+7 = 15,这时十位的值为5,1要进位,carry=1;最后看百位,我们手算的时候由于78没有百位的值所以百位的值为carry+6,但是计算机对齐的时候由于初始化为‘\0’所以百位对齐的值是‘\0‘会造成计算错误,所以,在大数相加的时候应该对较小的数补充前导0使两数位对齐。如果是999+999这种情况,要考虑新增的位数,所以我们把数组转置,让低位在前,高位在后计算。
二. 大数减法
与大数加法类似,只不过,不需要对齐数位,把进位改成借位了。
三. 大数乘法
大数乘法的实现要调用大数加法,想一下手算的时候怎么算??还是算678*78,手算的时候会先算8*678=5424,然后算7*678=4746,但是4746会错开一位去写,因为这里的7其实是70所以4746真实值为47460,然后把两个结果相加得到的最后乘法运算的结果为5424+47460 = 52884
四. 大数除法
(这里说的除法是一个较大的数除以较小的数,其它的都可以转化成这种算法或直接得出结果)
大数除法是这里最耗费时间,也是最复杂的算法了。
因为我们几乎无法去模拟真实的手算,因为无法试除,但想一想除法的本质,除法的本质其实就是多个减法,但如果我们循环相减的话实在太浪费时间了,计数就是一个麻烦的问题。
那要怎么办呢?仍然是对齐法,只不过这次我们把最高位对齐,其他位补0,就是相当于把除数乘以10的某次幂使数位对齐这样就减少了减法的次数,我们还可以继续优化,对齐之后乘以某个数使除数接近于被除数,大家可以试一下这个数最小是被除数的最高位除以除数最高位与1的和即 (a[0]-‘0’)/(b[0]-‘0’+1),而最大值为9,这样我们就能算出一个最接近与被除数的数,而除数乘上的这两个值(10的某次幂与这个数)相乘就是商的一部分,我们让被除数减去这个计算出来接近被除数的值,然后仍旧让原除数对齐,新被除数,依次循环直至运算结束,每次累加出来的和就是商。说着有些绕,大家手算一下就明白了。举个例子:8000/99,先对齐99→9900,然而8000-9900得0,结果累加为0 ,进行下一步,99→990,990乘以9~0之间某个值最接近8000,这个值为8,故为8000-990*8=80,商累加为0+8*10=80,新被除数为80,99→99,然而无法再减,最后结果为80.
五. 大数除一个整形数
完全模拟手算就可以,和大数加减差不多,主要为了特殊情况节约时间;
六. 大数幂
使用二分求幂的方法去计算,需要调用大数乘法,二分幂详见http://blog.csdn.net/tt2767/article/details/45479003
七. 大数求模
大数求模,原理就是对每一位循环利用同余定理
如果 C = A + B
那么C % mod =( A % mod + B % mod)%mod 成立
八. 大数阶乘
这个输入一个整型数就可以了,输出结果为大数形式,按位计算,结果存在字符串中就好,类似于大数加减
九. 大数比较
大数比较就很简单了,主要了对比长度,然后从高位到低位对比每一位就好
十. 一些需要注意的事
每一个运算中都存在这WA点可能使你的程序得到错误的结果,看看你是否注意到了下面的一些情况?
1. 特殊值的处理:当值为0或1的时候怎么处理?正值与负值怎么处理?
2. 每次运算是否都把结果保存了下来?
3. 新运算之前是否清空了保存结果的缓存区?
4. 需不需要增加前导0?需不需要删去前导0?进位时怎么处理?
5. 是否破坏了原始数据,有没有把原始数据拷贝或还原?
6. 是否考虑了每一步计算对后面产生的影响?
如果你能处理好这些问题,那么大数计算对你来说就不是问题了
大家可以先写一写自己的算法,然后按照后边的题自己测试一下,你写的正确与否,再查看模板,模板中已经详细注释了,请仔细查看!
**模板在最后,详细解析在模板的注释里面!!**
各大OJ上的练习题:
1.大数的加减乘除
加:hdu 1002A+B Problem II http://acm.hdu.edu.cn/showproblem.php?pid=1002
减:百炼2736 http://bailian.openjudge.cn/practice/2736/
乘:百炼2980 http://bailian.openjudge.cn/practice/2980/
除:百炼2737 http://bailian.openjudge.cn/practice/2737/
除法把主程序改成这样,其他的直接输入输出就好~
int main()
{
char a[N] = {'\0'},b[N]={'\0'};
int n;
scanf("%d",&n);
while(n--)
{
scanf("%s%s",a,b);
puts(BigDivBig(a,b));
}
return 0;
}
2.hdu 1042 N!
http://acm.hdu.edu.cn/showproblem.php?pid=1042
把模版里的主程序改成这样就过了 =。=
int main()
{
int n;
while(~scanf("%d",&n))
{
puts(BigFuc(n));
}
return 0;
}
3.poj3982 序列
http://poj.org/problem?id=3982
再把主程序改成这样 =。=
int main()
{
char ans[105][N] = {'\0'};
char temp[N]={'\0'};
int n,i;
while(~scanf("%s%s%s",ans[0],ans[1],ans[2]))
{
for(i = 3 ; i <=99 ; i++)
{
strcpy(temp,BigAdd(ans[i-3],ans[i-2]));
strcpy(ans[i],BigAdd(temp,ans[i-1]));
}
puts(ans[99]);
}
return 0;
}
4 . NYOJ 73 比大小
http://acm.nyist.net/JudgeOnline/problem.php?pid=73
改主程序。。。。。
int main()
{
char a[N] = {'\0'},b[N]={'\0'};
int n;
while(scanf("%s%s",a,b))
{
n = BigCmp(a,b);
if(n == 0 && a[0] == '0' && b[0] == '0')
break;
if(n == 0)
puts("a==b");
else if(n == 1)
puts("a);
else
puts("a>b");
}
return 0;
}
5.NYOJ 45 棋盘覆盖 (递推 + 大数)
http://acm.nyist.net/JudgeOnline/problem.php?pid=45
可以算出公式为 f[i] = 4 * f[i-1] + 1,用大数打个表加快速度
套用模板后,增加增加代表4和0的大数并修改主程序:
int main()
{
char four[2] = {'4','\0'};//增加代表4和0的大数
char one[2] = {'1','\0'};
char ans[105][N] = {'\0'};
char temp[N]={'\0'};
int n,i,k,Case;
ans[1][0] = '1';
ans[1][1] = '\0';
for(i = 2 ; i <= 100 ; i++)
{
strcpy(temp,BigMul(ans[i-1],four));
strcpy(ans[i],BigAdd(temp,one));
}
scanf("%d",&Case);
while(Case--)
{
scanf("%d",&k);
puts(ans[k]);
}
return 0;
}
6.hdu 1047 多组大数相加
http://acm.hdu.edu.cn/showproblem.php?pid=1047
这题表达的很乱,大致意思就是先输入一个整数代表数据组数,每组数据由0结尾,注意的是0后必有结果而且输出之间要有空行
这个模板因为是所以计算函数共用一个数组保存结果,故每以一计算都需要把结果保存下来!
int main()
{
char a[N] = {'\0'},ans[N] = {'\0'};
int Case;
int i,j;
scanf("%d",&Case);
while(Case--)
{
ans[0] = '0';
ans[1] = '\0';
while(~scanf("%s",a))
{
if(strlen(a) == 1 && a[0] == '0')
break;
strcpy(ans,BigAdd(a,ans));
}
puts(ans);
if(Case>0)
puts("");
}
return 0;
}
7 . hdu1715 大菲波数
http://acm.hdu.edu.cn/showproblem.php?pid=1715
这个题和上边那个“棋盘覆盖”的题是一样的,只不过递推公式变了,其实还能更快求更大的斐波那契数(一亿多),只不过那需要用到矩阵递推了。
下面是我的模板,讲解都在注释里面了,仔细看!
#include//不能连续复合运算,要保存结果
#include//所以一个技巧就是用strcpy来代替等号
#include//c=a+b改写为strcpy(c,BigAdd(a,b))
#define BASE 10 //确定进制
#define N 90001 //确定 最大位数+1
int l = 0; //每次记录缓存区用了多长,还原时节省时间
char res[N] = { '\0' }; //保存结果
void ini(char * x , int l); //初始化x数组
int BigCmp(char * a, char * b); // 大数a < 大数b 返回1 ,相等返回0 ,a>b返回-1
void Clean(char * x, int l);//清除尾部的‘0’
void rev(char * x);//倒置字符串 ,与Clean联用,清除前导0
char * BigAdd(char * a, char * b);
char * BigSub(char * a,char * b);
char * BigMul(char * a,char * b);
char * BigDivNum(char * num,int n);//大数除以一个int
char * BigDivBig(char * a,char * b);//大数除以一个大数
char * BigPow(char *num ,int n);
char * BigMod(char * num , int mod);
char * BigFuc(int num);
int main()
{
char a[N] = {'\0'},b[N]={'\0'};
int n;
while(~scanf("%s%s%d",a,b,&n))
{
puts("a + b得:");
puts(BigAdd(a,b));
puts("a - b 得:");
puts(BigSub(a,b));
puts("a * b 得:");
puts(BigMul(a,b));
puts("a / b 得:");
puts(BigDivBig(a,b));
puts("a / n 得:");
puts(BigDivNum(a,n));
puts("a^n 得:");
puts(BigPow(a,n));
puts("a % n 得:");
puts(BigMod(a,n));
puts("n! 得:");
puts(BigFuc(n));
puts("");
}
return 0;
}
char * BigAdd( char * a, char * b)
{
int i,j,k;
int sum,la,lb,carry,flag,cmp;
char *ans,*temp;
ini(res,l);
carry = 0;
flag = 0;
ans = &res[1];
if(a[0] == '-' && b[0] != '-') //判断正负
return BigSub(b,a+1);
else if(a[0] != '-' && b[0] == '-')
return BigSub(a,b+1);
else if(a[0] == '-' && b[0] == '-')
{
flag = 1;
a++;
b++;
}
la=strlen(a);
lb=strlen(b);
if(b[0] == '0' && lb == 1) //判断0
{
strcpy(ans,a);
l = strlen(ans);
return ans;
}
else if(a[0] == '0' && la ==1)
{
strcpy(ans,b);
l = strlen(ans);
return ans;
}
rev(a);
rev(b);
if(BigCmp(a,b) == 1) //保持大数a>大数b
{
temp = a;
a = b;
b =temp;
k = la;
la = lb;
lb = k;
}
for(i = lb ; i < la ; i++) //空位补0
b[i] = '0';
for(i = 0 ; i < la ; i++)
{
sum = (a[i]-48) + (b[i]-48) + carry;
if( sum < BASE )
{
ans[i] = sum + 48;
carry = 0;
}
else
{
ans[i] = sum - BASE + 48;
carry = 1;
}
}
if(carry) //补充最高位
{
ans[i] = carry + 48;
i++;
}
Clean(ans,i);
for(i = lb ; i < la ; i++)//删除后补上的0
b[i] = '\0';
rev(ans);
rev(a);
rev(b);
if(flag)
{
res[0] = '-';
ans = res;
}
l = strlen(ans);
return ans;
}
char * BigSub(char * a,char * b)
{
char *ans,*temp;
int i,j,k;
int borrow,flag,la,lb,sub,cmp;
ini(res,l);
ans = &res[1];
flag = 0; //结果没有负号
borrow = 0;
if(a[0]=='-' && b[0]!='-') //被减数为负,减数为正,结果为负
{
BigAdd(b,a+1);
res[0] = '-';
return res;
}
else if(a[0]!='-' && b[0]=='-') //被减数为正,减数为负,结果为正
return BigAdd(a,b+1);
else if(a[0]=='-' && b[0]=='-') //如果a,b为同时负,交换他们并都改为正,保证为“a-b”的形式
{
temp=a;
a=b;
b=temp;
a++;
b++;
}
la = strlen(a);
lb = strlen(b);
if(b[0] == '0' && lb == 1) //判断0
{
l = strlen(strcpy(ans,a));
return ans;
}
else if(a[0] == '0' && la == 1)
{
if(b[0] == '-')
{
l = strlen(strcpy(ans,b+1));
return ans;
}
else
{
res[0] = '-';
l = strlen(strcpy(ans,b));
return res;
}
}
cmp = BigCmp(a,b);
if(cmp == 0)
{
l = 1;
res[0] = '0';
res[1] = '\0';
return res;
}
else if(cmp == 1) //保持大数a>=大数b
{
temp=a;
a=b;
b=temp;
flag=1; //结果有负号
k = la;
la = lb;
lb = k;
}
rev(a);
rev(b);
for(i=0; iif( sub >= 0)
{
ans[i] = sub + 48;
borrow = 0 ;
}
else // 溢出时的计算方法
{
ans[i] = sub + BASE + 48;
borrow = 1;
}
}
while(i < la) // 计算剩余位
{
sub = a[i] - borrow ;
if(a[i] >= borrow)
{
ans[i] = sub ;
borrow = 0;
}
else // 溢出时的计算方法
{
ans[i] = sub +BASE ;
borrow = 1;
}
i++;
}
Clean(ans,i);
rev(ans);
rev(a);
rev(b);
if(flag)
{
res[0] = '-';
ans = res;
}
l = strlen(ans);
return ans;
}
char * BigMul(char * a,char * b)
{
char *temp,*ans;
char mul[N] = {'\0'},cal[N] = {'\0'},num[N] = {'\0'};
int i,j,k;
int carry,flag,la,lb,product,lmul;
int sign,sign_a,sign_b;
ini(res,l);
ans = &res[1];
carry = 0;
flag = 0;
sign = sign_a = sign_b = 0;
if(a == b) //重复拷贝
b = strcpy(num,a);
if(a[0] == '-' )
{
flag = 1;
sign_a = 1;
a++;
}
if(b[0] == '-' )
{
flag = 1;
sign_b = 1;
b++;
}
if(sign_a && sign_b)
flag = 0;
la = strlen(a);
lb = strlen(b);
if((a[0] == '0' && la == 1) || (b[0] == '0' && lb == 1)) //任何一个大数为0,结果为0
{
l = 1;
res[0] = '0';
res[1] = '\0';
return res;
}
if(BigCmp(a,b) == 1) //保证大数a >= 大数b
{
temp = a;
a = b ;
b = temp;
k = la;
la = lb;
lb =k;
}
rev(a);
rev(b);
Clean(a,la);//清除自带的前导0
Clean(b,lb);
la = strlen(a);//重新计算长度
lb = strlen(b);
lmul = 0;
for(i = 0 ; i < lb ; i++)
{
ini(mul,lmul);
for( j = 0 ; j < la ; j++)
{
product = (a[j] - 48) * (b[i] - 48) + carry ;
mul[j] = product % BASE + 48 ;
carry = product / BASE ;
}
if(carry)
{
mul[j] = carry + 48;
j++;
carry = 0;
}
lmul = j; //计算缓冲区长度
if(i == 0)
{
strcpy(cal,mul);
rev(cal);
}
else
{
//清除前导0
Clean(mul,lmul);
//翻转字符串
rev(mul);
//以0补位,每次相当于乘10
for(k = 0 ; k < i ;k++)
mul[lmul++] = '0';
//保存
ans = BigAdd(cal,mul);
ini(cal,strlen(cal));
strcpy(cal,ans);
}
}
strcpy(ans,cal);
rev(a);
rev(b);
if(flag)
{
res[0] = '-';
ans = res;
}
l = strlen(ans);
return ans;
}
char * BigDivBig(char * a,char * b)
{
char *ans, *temp;
int i,j,k,la,lb,cmp,times,tran_min;
int sign,sign_a,sign_b,flag;
ini(res,l);
ans = &res[1];
sign_a = sign_b = flag = 0;
if(a[0] == '-')
{
sign_a = flag = 1;
a++;
}
if(b[0] == '-')
{
sign_b = flag = 1;
b++;
}
if(sign_a && sign_b)
flag = 0;
la = strlen(a);
lb = strlen(b);
rev(a);
rev(b);
Clean(a,la);
Clean(b,lb);
rev(a);
rev(b);
la = strlen(a);
lb = strlen(b);
if(la == 1 && a[0] == '0')
{
l = 1;
res[0] = '0';
res[1] = '\0';
return res;
}
if(lb == 1 && b[0] == '0')
{
puts("除数不能为0!");
exit(1);
}
if(lb == 1 && b[0] == '1')
{
l = strlen(strcpy(res,a));
return res;
}
cmp = BigCmp(a,b);
if(cmp == 1)
{
l = 2;
ans[0] = '0';
ans[1] = '\0';
return ans;
}
else if(cmp == 0)
{
l = 2;
ans[0] = '1';
ans[1] = '\0';
if(flag)
{
res[0]='-';
ans = res;
}
return ans;
}
///对齐最高位试除
char pre[N] = {'\0'},num[N] = {'\0'},now[N] = {'\0'},tran[N] = {'\0'};
char mul[N] = {'\0'},sub[N] = {'\0'},div[N]={'\0'};
char carry[N] = {'0'};///注意是 0
memset(carry,'0',sizeof(carry));
now[0] = '0'; //大数now初始化为0
carry[0] = '1';//大数carry初始化为1000000000……
times = la - lb;//确定最高倍数
strcpy(num,a); //复制a的值
strcpy(div,b);//复制b的值
for(i = 0 ; i < times ; i++) //对齐最高位
div[lb+i] = '0';
for(i = times ; i >= 0 ;i--)
{ //最小的倍数为:a最高位/(b最高位+1),试一下就知道了
tran_min = (num[0] - '0')/(div[0]-'0'+1); ///?????
for(j = 9 ; j >= tran_min ; j--)
{
sprintf(tran,"%d",j); //把试乘值转换成字符串
strcpy(pre,BigMul(div,tran));
strcpy(sub,BigSub(num,pre)); //sub = a - b*j;
//从大到小找第一个可以被减的数
if(sub[0] != '-')
{
carry[i+1] = '\0'; //截断carry得到倍数
strcpy(mul,BigMul(carry,tran));//乘上这次的值
strcpy(now,BigAdd(now,mul));//将商保存在now中
strcpy(num,sub); //更新a的值
break;
}
}
div[lb+i-1] = '\0'; //每次循环降低一位直至复原
memset(res,'\0',sizeof(res));
}
strcpy(ans,now);
if(flag)
{
res[0] = '-';
ans = res;
}
l = strlen(ans);
return ans;
}
char * BigDivNum(char * num , int n)
{
char * ans;
int i,j,digit,divis,lnum,flag;
int sign_num,sign_n;
ini(res,l);
ans = &res[1];
digit = divis = flag = 0;
sign_num = sign_n = 0;
if(n == 0)
{
puts("除数不能等于0");
exit(1);
}
if(n < 0)
{
n = -n;
flag = 1;
sign_n = 1;
}
if(num[0] == '-')
{
num++;
flag = 1;
sign_num = 1;
}
if(sign_n && sign_num)
flag = 0;
lnum = strlen(num);
if(lnum == 1 && num[0] == '0')
{
l = 1;
res[0] = '0';
res[1] = '\0';
return res; //被除数为0,结果为0
}
for(i = 0 ; i < lnum ; i++)
{
divis = divis * 10 + (num[i]-48) ;
if(divis >= n)
{
ans[digit++] = divis / n + 48;
divis %= n;
}
}
if(!digit)
ans[digit++] = '0';
ans[digit] = '\0';
if(flag)
{
res[0] = '-';
ans = res;
}
l = strlen(ans);
return ans;
}
char * BigPow(char * num, int n)
{
char * ans;
char cal[N] = {'\0'},pow[N] = {'\0'};
int flag;
if(n == 0)
{
l = 1;
res[0] = '1';
res[1] = '\0';
return res;
}
if(strlen(num) == 1 && num[0] == '0')
{
l = 1;
res[0] = '0';
res[1] = '\0';
return res;
}
ini(res,l);
ans = &res[1];
flag = 0;
strcpy(pow,num); //备份
cal[0] = '1';
if(pow[0] == '-' && n&1)
flag = 1;
while(n)
{
if(n&1)
{
ans = BigMul(cal,num);
ini(cal,strlen(cal));
strcpy(cal,ans);
}
ans = BigMul(num,num);
ini(num,strlen(num));
strcpy(num,ans);
n >>= 1;
}
strcpy(num,pow); //还原
strcpy(ans,cal);
if(flag)
{
res[0] = '-';
ans = res;
}
l = strlen(ans);
return ans;
}
char * BigMod(char * num , int mod)
{
char * ans;
int i,lnum,cal;
if(mod == 0)
{
puts("除数不能等于0");
exit(1);
}
ini(res,l);
ans = &res[1];
cal = 0;
lnum = strlen(num);
for(i = 0 ; i < lnum ; i++) //循环利用同余定理
cal = ( ((cal*BASE)%mod) + ((num[i]-48)%mod) ) % mod ;
sprintf(ans,"%d",cal); //转换为字符串
l = strlen(ans);
return ans;
}
char * BigFuc(int num)
{
int i,j,k;
int digit,carry,temp;
char * ans;
ini(res,l);
ans = &res[1];
if(num == 0)
{
l = 1;
res[0] = '1';
res[1] = '\0';
return res;
}
ans[0] = '1';
digit = 1;
for(i = 2 ; i <= num ; i++)
{
carry = 0; //初始化为0
for(j = 0 ; j < digit ; j++)
{
temp = (ans[j]-48)* i + carry; //中间值
ans[j] = temp % BASE +48 ;
carry = temp / BASE;
}
while(carry) //处理剩余位数
{
ans[digit++] = carry % BASE + 48;
carry /= BASE ;
}
}
rev(ans);
l = strlen(ans);
return ans;
}
int BigCmp(char * a, char * b) // 大数a < 大数b 返回1 ,相等返回0 ,a>b返回-1
{
int i,j,k;
int la = strlen(a),lb = strlen(b);
char * temp;
if(a[0] == '-' && b[0] != '-')
return 1;
else if(a[0] != '-' && b[0] == '-')
return -1;
else if(a[0] == '-' && b[0] == '-')
{
a++;
b++;
temp = a;
a = b;
b = temp;
la--;
lb--;
k = la;
la =lb;
lb = k;
}
if(la < lb)
return 1;
if(la > lb)
return -1;
if(la == lb)
{
for(i = 0 ; i < la ; i++)
{
if(a[i] < b[i])
return 1;
else if(a[i] > b[i])
return -1;
}
}
return 0;
}
void ini(char * x ,int l)
{
int i;
if(l < N)
l++;
for(i = 0 ; i < l ;i++)
x[i] = '\0';
}
void Clean(char * x,int l)
{
for(l--; x[l] == '0';l--)
x[l] = '\0';
}
void rev(char * x)
{
int right = strlen(x)-1;
int left = 0;
char temp;
while(left < right)
{
temp = x[left];
x[left++] = x[right];
x[right--] = temp;
}
}