在做题的过程中,我们经常会遇到这样一种情况,就是题目中给出的数据大的离谱,一般都是几十、上百位,毫无疑问,一般的数据类型如int、longlong、__int64等,是无法接收这些高精度数据的。很明显,如果不使用一些特殊的方法对这些数据进行处理,那就别想后面再写出什么算法来解决这道题!
说到这里,那到底用什么方法来接收这些大数据呢?答案是字符串,因为字符占用的空间比整型小。没错,就是先把数据当成字符串完整的接收下来,然后再对它进行处理。一般来说,我们的以字符串形式接收下来的对象,都会或多或少的参与运算,但是字符串游怎么参与运算呢?答案是重载运算符,按照运算规则重新实现各类运算。下面给出一份高精度类的模版,已经实现的运算有=、+、-、*、/、%、+=、-=、*=、/=、%=、power、sqrt、==、!=、>、<、>=、<=、>>、<<(可以继续扩展):
const int MAXN = 9999;
class bign
{
public:
int len; // 长度
int data[100]; // 控制大数的位数
bign () {len=1; memset(data, 0, sizeof(data));} // 构造函数
bign (const int b) // 构造大数对象(整型)
{
int c,d = b;
len = 0;
memset(data, 0, sizeof(data));
while(d > MAXN)
{
c = d - (d / (MAXN + 1)) * (MAXN + 1);
d = d / (MAXN + 1);
data[len++] = c;
}
data[len++] = d;
}
bign (const char *s) // 构造大数对象(字符串)
{
int t,k,index,l,i;
memset(data, 0, sizeof(data));
l=strlen(s);
len=l/MAXN;
if(l%MAXN)
len++;
index=0;
for(i=l-1;i>=0;i-=MAXN)
{
t=0;
k=i-MAXN+1;
if(k<0)
k=0;
for(int j=k;j<=i;j++)
t=t*10+s[j]-'0';
data[index++]=t;
}
}
bign (const bign &T) : len(T.len) // 拷贝构造函数(大数)
{
int i;
memset(data, 0, sizeof(data));
for(i=0; i<len; ++i)
data[i] = T.data[i];
}
/*bign operator = (const bign &n) // 重载=(大数)
{
int i;
len = n.len;
memset(data,0,sizeof(data));
for(i = 0 ; i < len ; i++)
data[i] = n.data[i];
return *this;
}
bign operator = (const int num) // 重载=(整型)
{
char s[MAXN];
sprintf(s, "%d", num);
*this = s;
return *this;
}
bign operator = (const char *num) // 重载=(字符串)
{
for(int i=0; num[i]=='0'; ++num); // 去前导0
len = strlen(num);
for(int i = 0; i < len; ++i)
data[i] = num[len-i-1] - '0';
return *this;
}*/
bign operator + (const bign &T) // 重载+(大数)
{
bign t(*this);
int i, big; //位数
big = T.len > len ? T.len : len;
for(i = 0 ; i < big ; i++)
{
t.data[i] += T.data[i];
if(t.data[i] > MAXN)
{
t.data[i + 1]++;
t.data[i] -=MAXN+1;
}
}
if(t.data[big] != 0)
t.len = big + 1;
else
t.len = big;
return t;
}
bign operator - (const bign &T) // 重载-(大数),注意需要大减小
{
int i,j,big;
bool flag;
bign t1,t2;
if(*this>T)
{
t1=*this;
t2=T;
flag=0;
}
else
{
t1=T;
t2=*this;
flag=1;
}
big=t1.len;
for(i = 0 ; i < big ; i++)
{
if(t1.data[i] < t2.data[i])
{
j = i + 1;
while(t1.data[j] == 0)
j++;
t1.data[j--]--;
while(j > i)
t1.data[j--] += MAXN;
t1.data[i] += MAXN + 1 - t2.data[i];
}
else
t1.data[i] -= t2.data[i];
}
t1.len = big;
while(t1.data[t1.len - 1] == 0 && t1.len > 1)
{
t1.len--;
big--;
}
if(flag)
t1.data[big-1]=0-t1.data[big-1];
return t1;
}
bign operator * (const bign &T) // 重载*(大数)
{
bign ret;
int i,j,up;
int temp,temp1;
for(i = 0 ; i < len ; i++)
{
up = 0;
for(j = 0 ; j < T.len ; j++)
{
temp = data[i] * T.data[j] + ret.data[i + j] + up;
if(temp > MAXN)
{
temp1 = temp - temp / (MAXN + 1) * (MAXN + 1);
up = temp / (MAXN + 1);
ret.data[i + j] = temp1;
}
else
{
up = 0;
ret.data[i + j] = temp;
}
}
if(up != 0)
ret.data[i + j] = up;
}
ret.len = i + j;
while(ret.data[ret.len - 1] == 0 && ret.len > 1)
ret.len--;
return ret;
}
bign operator / (const int &b) // 重载/(整型)
{
bign ret;
int i, down=0;
for(i = len - 1 ; i >= 0 ; i--)
{
ret.data[i] = (data[i] + down * (MAXN + 1)) / b;
down = data[i] + down * (MAXN + 1) - ret.data[i] * b;
}
ret.len = len;
while(ret.data[ret.len - 1] == 0 && ret.len > 1)
ret.len--;
return ret;
}
bign operator / (const bign &b) // 重载/(大数)
{
bign c, f = 0;
for(int i=len-1; i>=0; --i)
{
f = f*10;
f.data[0] = data[i];
while(f >= b)
{
f -= b;
c.data[i]++;
}
}
c.len = len;
c.clean();
return c;
}
int operator % (const int &b) // 重载%(整型)
{
int i,d=0;
for (i = len-1; i>=0; i--)
{
d = ((d * (MAXN+1))% b + data[i])% b;
}
return d;
}
bign operator >> (const int &k)
{
bign t(*this);
int p = k;
while(p)
{
t = t/2;
p--;
}
return t;
}
bign operator << (const int &k)
{
bign t(*this);
int p = k;
while(p)
{
t = t*2;
p--;
}
return t;
}
bign operator += (const bign &b) // 重载+=(大数)
{
*this = *this + b;
return *this;
}
bign operator -= (const bign &b) // 重载-=(大数)
{
*this = *this - b;
return *this;
}
bign operator *= (const bign &b) // 重载*=(大数)
{
*this = *this * b;
return *this;
}
bign operator /= (const bign &b) // 重载/=(大数)
{
*this = *this / b;
return *this;
}
bign operator %= (const int &b) // 重载%=(整型)
{
*this = *this % b;
return *this;
}
bool operator == (const bign &T) // 重载==(大数)
{
if(len!=T.len) return false;
int ln = len-1;
int flag = 0;
while(ln >= 0 && data[ln] == T.data[ln]) ln--;
if(ln == -1) return true;
return false;
}
bool operator == (const int &t) // 重载==(整型)
{
bign b(t);
return *this==b;
}
bool operator != (const bign &b) // 重载!=(大数)
{
return !(*this == b);
}
bool operator > (const bign &T) // 重载>(大数)
{
int ln;
if(len > T.len)
return true;
else if(len == T.len)
{
ln = len - 1;
while(ln >= 0 && data[ln] == T.data[ln])
ln--;
if(ln >= 0 && data[ln] > T.data[ln])
return true;
else
return false;
}
else
return false;
}
bool operator > (const int &t) // 重载>(整型)
{
bign b(t);
return *this>b;
}
bool operator < (const bign &T) // 重载<(大数)
{
if(!(*this>T)&&!(*this==T)) return true;
return false;
}
bool operator < (const int &t) // 重载<(整型)
{
bign b(t);
return *this<b;
}
bool operator <= (const bign &b) // 重载<=(大数)
{
return *this < b || *this == b;
}
bool operator >= (const bign &b) // 重载>=(大数)
{
return *this > b || *this == b;
}
bign power(const int &n) // 大数的n次方运算
{
bign t,ret(1);
int i;
if(n<0)
exit(-1);
if(n==0)
return 1;
if(n==1)
return *this;
int m=n;
while(m>1)
{
t=*this;
for( i=1;i<<1<=m;i<<=1)
{
t=t*t;
}
m-=i;
ret=ret*t;
if(m==1)
ret=ret*(*this);
}
return ret;
}
typedef int bignum_t[MAXN+1];
void sub(bignum_t a,const bignum_t b,const int c,const int d)
{
int i,O=b[0]+d;
for (i=1+d;i<=O;i++)
if ((a[i]-=b[i-d]*c)<0)
a[i+1]+=(a[i]-MAXN+1)/MAXN,a[i]-=(a[i]-MAXN+1)/MAXN*MAXN;
for (;a[i]<0;a[i+1]+=(a[i]-MAXN+1)/MAXN,a[i]-=(a[i]-MAXN+1)/MAXN*MAXN,i++);
for (;!a[a[0]]&&a[0]>1;a[0]--);
}
int comp(const bignum_t a,const int c,const int d,const bignum_t b)
{
int i,t=0,O=-MAXN*2;
if (b[0]-a[0]<d&&c)
return 1;
for (i=b[0];i>d;i--)
{
t=t*MAXN+a[i-d]*c-b[i];
if (t>0) return 1;
if (t<O) return 0;
}
for (i=d;i;i--)
{
t=t*MAXN-b[i];
if (t>0) return 1;
if (t<O) return 0;
}
return t>0;
}
void sqrt(bignum_t b,bignum_t a) // 计算b的a次方根
{
int h,l,m,i;
memset((void*)b,0,sizeof(bignum_t));
for(i=b[0]=(a[0]+1)>>1;i;sub(a,b,m,i-1),b[i]+=m,i--)
for(h=MAXN-1,l=0,b[i]=m=(h+l+1)>>1;h>l;b[i]=m=(h+l+1)>>1)
if(comp(b,m,i-1,a)) h=m-1;
else l=m;
for(;!b[b[0]]&&b[0]>1;b[0]--);
for(i=1;i<=b[0];b[i++]>>=1);
}
string str () // 将内部的字符数组转换成string并返回
{
string res = "";
for(int i=0; i<len; ++i)
res = char(data[i]+'0') + res;
return res;
}
void clean()
{
while(len>1 && !data[len-1]) len--;
}
// 重载输入、输出流,这样我们可以更方便操作高精度对象
friend istream& operator >> (istream &in, bign &b)
{
char ch[MAXN*4];
int i = -1;
in>>ch;
int l=strlen(ch);
int count=0,sum=0;
for(i=l-1;i>=0;)
{
sum = 0;
int t=1;
for(int j=0;j<4&&i>=0;j++,i--,t*=10)
{
sum+=(ch[i]-'0')*t;
}
b.data[count]=sum;
count++;
}
b.len =count++;
return in;
}
friend ostream& operator << (ostream &out, bign &b)
{
int i;
cout << b.data[b.len - 1];
for(i = b.len - 2 ; i >= 0 ; i--)
{
cout.width(MAXN);
cout.fill('0');
cout << b.data[i];
}
return out;
}
};
顺便说一句,在Java中,已经有现成的高精度类可以使用了,它们是大整型类BigInteger,大浮点型类BigDecimal,但是作为搞算法的我们来说,了解它们的实现还是有必要的,作为扩展,我下面还是给出Java的模版吧,详细的应用在这里(点击打开链接):
import java.math.*; // BigDecimal和BigInteger两个类,都包含在java.math.*里面
public class Main {
public static void main(String[] args){
BigInteger x = new BigInteger("123456789123456789123456789");
BigInteger y = new BigInteger("123456789123456789123456789");
System.out.println(x .add(y)); // 加
/*BigInteger其它常用函数:
subtract(BigInteger val) 减
multiply(BigInteger val) 乘
divide(BigInteger val) 除
abs() 绝对值
compareTo(BigInteger val) 比较
pow(int exponent) 幂
gcd(BigInteger val) 最大公约数
mod(BigInteger val) 模
*/
BigDecimal p = new BigDecimal("123456789123456789123456789.123456789");
BigDecimal q = new BigDecimal("123456789123456789123456789.123456789");
System.out.println(x .add(y));
/*BigDecimal其它常用函数:
BigDecimal(String val) 将 BigDecimal 的字符串表示形式转换为 BigDecimal。
abs() 返回 BigDecimal,其值为此 BigDecimal 的绝对值,其标度为 this.scale()。
add(BigDecimal augend) 返回一个 BigDecimal,其值为 (this + augend),其标度为 max(this.scale(), augend.scale())。
compareTo(BigDecimal val) 将此 BigDecimal 与指定的 BigDecimal 比较。
setScale(int newScale, RoundingMode roundingMode) 返回 BigDecimal,其标度为指定值,其非标度值通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定,以维护其总值。
subtract(BigDecimal subtrahend) 返回一个 BigDecimal,其值为 (this - subtrahend),其标度为 max(this.scale(), subtrahend.scale())。
divide(BigDecimal divisor, RoundingMode roundingMode) 返回一个 BigDecimal,其值为 (this / divisor),其标度为 this.scale()。
RoundingMode.
CEILING 向正无限大方向舍入的舍入模式。
DOWN 向零方向舍入的舍入模式。
FLOOR 向负无限大方向舍入的舍入模式。
HALF_DOWN 向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。
HALF_EVEN 向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。
HALF_UP 向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。
UNNECESSARY 用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。
UP 远离零方向舍入的舍入模式。
*/
// 详细API使用说明请查看相应文档
}
}
Divided Land
Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 477 Accepted Submission(s): 253
Each case contains two binary number represents the length L and the width W of given land. (0 < L, W ≤ 2 1000)
3 10 100 100 110 10010 1100
Case #1: 10 Case #2: 10 Case #3: 110
就是说给你一块长方形,告诉你长和宽,并且都是以二进制表示的,然后在没有浪费的情况下,将它切成多个相同的正方形,最后让求正方形可能的最大边长,答案也要用二进制表示。
分析:
拿到题后,我们首先发现的是数据的极值尽然是2^1000,这个数大约是10^300,于是便自然而然想到了高精度类。这样,数据存储便搞定了,但是数据却是以二进制表示的,这样似乎让我们摸不着头脑,那先假设数据是十进制。然后题目是要将长方形划分为正方形,又不能有浪费,很显然正方形边长只有能将长方形长和宽整除才能满足条件,即边长是长和宽的公约数,并且又要保证边长最大,那就最大公约数哇!但是,我们熟知的gcd算法只能处理一般的数据,即范围不大的十进制数,但是本题中数据是高精度,那么就涉及到高精度的gcd算法,具体方法看另一篇文章,传送门(点击打开链接);而对于二进制的问题,它与十进制之间的转换是比较方便的,因为只涉及到2的幂次,具体看代码。本题代码如下:
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;
const int MAXN = 9999;
class bign
{
// 如前面所示的大数类
};
bool iseven (bign x)
{
if(x.data[0]%2==0)
return true;
return false;
}
bign gcd (bign a, bign b)
{
bign k=1, ans;
while (1)
{
if (a == 0)
{ans = b; break;}
if (b == 0)
{ans = a; break;}
if (a < b)
{
bign temp = a;
a = b;
b = temp;
}
else
{
if (iseven(a))
{
if (iseven(b))
{
a = a/2;
b = b/2;
k=k*2;
}
else a = a/2;
}
else
{
if (iseven(b)) b = b/2;
else a = (a-b)/2;
}
}
}
return ans*k;
}
int main()
{//freopen("sample.txt","r",stdin);
int t, i, j, cas=1;
char a[2005], b[2005];
scanf("%d", &t);
while (t--)
{
scanf ("%s %s", a, b);
bign temp(1), t1(0), t2(0);
int len = strlen(a);
for (i=len-1; i>=0; --i)
{
if(a[i] == '1') // 从二进制最右边的一位累加,如111=4+2+1
t1 = t1 + temp;
temp = temp*2;
}
len = strlen(b);
temp = 1;
for (i=len-1; i>=0; --i) // 将b也转换为十进制
{
if(b[i] == '1')
t2 = t2+temp;
temp = temp*2;
}
int tt[2005];
bign ans = gcd(t1, t2);
int k = 0;
while (ans != 0) // 将十进制转换为二进制
{
if(ans%2 == 1)
tt[k++] = 1;
else tt[k++] = 0;
ans = ans / 2;
}
printf("Case #%d: ", cas++);
for (i=k-1; i>=0; --i)
printf("%d", tt[i]);
printf("\n");
}
return 0;
}
下面顺便也把Java写的代码贴出来吧,因为它的确是很简便:
import java.math.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
BigInteger a, b;
int cas = cin.nextInt();
for(int i=1; i<=cas; ++i)
{
a = cin.nextBigInteger(2); // 以二进制输入
b = cin.nextBigInteger(2);
a = a.gcd(b);
System.out.println("Case #"+i+": "+a.toString(2)); // 以二进制输出
}
}
}
大数到这就告一段落了,现在再附上几道题,以作为练习,HDOJ:1042、1250、3546。