C++高精度模板学习指南
实现高精度计数器(朴素复杂度)
学习于:OI Wiki,并总结于适合自己高精度模板
[problem description]
[input]
一个形如 a <op> b
的表达式。
a、b 分别是长度不超过1000的十进制非负整数;
<op>
是一个字符(+
、-
、*
或 /
),表示运算。
整数与运算符之间由一个空格分隔。
[output]
对于 +
、-
、*
运算,输出一行表示结果;
对于 /
运算,输出两行分别表示商和余数。
保证结果均为非负整数。
[solved]
[input+output]
时间复杂度:O(n)
在平常的实现中,高精度数字利用字符串表示,每一个字符表示数字的一个十进制位。因此可以说,高精度数值计算实际上是一种特别的字符串处理。
读入字符串时,数字最高位在字符串首(下标小的位置)。但是习惯上,下标最小的位置存放的是数字的 最低位 ,即存储反转的字符串。这么做的原因在于,数字的长度可能发生变化,但我们希望同样权值位始终保持对齐(例如,希望所有的个位都在下标 [0]
,所有的十位都在下标 [1]
……);同时,加、减、乘的运算一般都从个位开始进行(回想小学的竖式运算),这都给了「反转存储」以充分的理由。
此后我们将一直沿用这一约定。定义一个常数 LEN = 1004
表示程序所容纳的最大长度。
const int LEN = 1004;
//清空大数
void clear(int a[]) {
for (int i = 0; i < LEN; i++) a[i] = 0;
}
//读入大数,a存储的是大数的反转
void read(int a[]) {
static char s[LEN + 1];
scanf("%s", s);
clear(a);
int len = strlen(s);
for (int i = 0; i < len; i++) a[len - 1 - i] = s[i] - '0';
}
//输出大数,考虑末尾的前导零,当大数为0会输出0
void print(int a[]) {
int i;
for (i = LEN - 1; i >= 1; i--) {
if (a[i] != 0) break;
}
for (; i >= 0; i--) putchar(a[i] + '0');
putchar('\n');
}
[add]
时间复杂度:O(n)
//高精度加法
void add(int a[], int b[], int c[]) {
clear(c);
// 高精度实现中,一般令数组的最大长度 LEN 比可能的输入大一些
// 然后略去末尾的几次循环,这样一来可以省去不少边界情况的处理
// 因为实际输入不会超过 1000 位,故在此循环到 LEN - 1 = 1003 已经足够
for (int i = 0; i < LEN - 1; i++) {
c[i] += a[i] + b[i];
if (c[i] >= 10) {
c[i + 1] += 1;
c[i] -= 10;
}
}
}
[sub]
时间复杂度:O(n)
//高精度减法,处理a>=b的情况,如果a<b:-sub(b,a,c)即可。
void sub(int a[], int b[], int c[]) {
clear(c);
for (int i = 0; i < LEN - 1; i++) {
c[i] += a[i] - b[i];
if (c[i] < 0) {
c[i + 1] -= 1;
c[i] += 10;
}
}
}
[mul_short]
时间复杂度:O(n)
//高精度-单精度乘法
//这里只考虑结果为正数的情况,如果结果为负,需要自行处理
//每一位被乘上之后都可能达到 9b 的数量级,所以我们特别需要关注乘数b的范围。
//若它和10^9属于同一数量级,谨慎使用
void mul_short(int a[], int b, int c[]) {
clear(c);
for (int i = 0; i < LEN - 1; i++) {
c[i] += a[i] * b;
if (c[i] >= 10) {
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
}
[mul]
时间复杂度:O(n^2)
//高精度-高精度乘法
//我们的算法在每一步乘的过程中并不进位,而是将所有的结果保留在对应的位置上,到最后再统一处理进位,但这不会影响结果。
void mul(int a[], int b[], int c[]) {
clear(c);
for (int i = 0; i < LEN - 1; i++) {
// 这里直接计算结果中的从低到高第 i 位,且一并处理了进位
// 第 i 次循环为 c[i] 加上了所有满足 p + q = i 的 a[p] 与 b[q] 的乘积之和
// 这样做的效果和直接进行上图的运算最后求和是一样的,只是更加简短的一种实现方式
for (int j = 0; j <= i; j++) c[i] += a[j] * b[i - j];
if (c[i] >= 10) {
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
}
[div]
时间复杂度:O(n^2)
//高精度除法,a:被除数,b:除数,c:商,d:余数
//为了减少冗余运算,我们提前得到被除数的长度 l_a 与除数的长度 l_b,
//从下标 l_a - l_b 开始,从高位到低位来计算商。
//这和手工计算时将第一次乘法的最高位与被除数最高位对齐的做法是一样的。
//参考程序实现了一个函数 greater_eq() 用于判断被除数以下标 last_dg 为最低位,
//是否可以再减去除数而保持非负。此后对于商的每一位,不断调用 greater_eq(),
//并在成立的时候用高精度减法从余数中减去除数,也即模拟了竖式除法的过程。
bool greater_eq(int a[], int b[], int last_dg, int len) {
//当a[last_dg,la]剩余的长度比除数长,并且这个情况下最多多出1位,故可此判断。
if (a[last_dg + len] != 0) return true;
for (int i = len - 1; i >= 0; i--) {
if (a[last_dg + i] > b[i]) return true;
if (a[last_dg + i] < b[i]) return false;
}
return true;
}
void div(int a[], int b[], int c[], int d[]) {
clear(c);
clear(d);
int la, lb;
//抹去a的前导零
for (la = LEN - 1; la > 0; la--) {
if (a[la - 1] != 0) break;
}
//抹去b的前导零
for (lb = LEN - 1; lb > 0; lb--) {
if (b[lb - 1] != 0) break;
}
//如果b为0,直接退出
if (lb == 0) {
puts("><");
return;//除数不能为零
}
// c 是商
// d 是被除数的剩余部分,算法结束后自然成为余数
for (int i = 0; i < la; i++) d[i] = a[i];
for (int i = la - lb; i >= 0; i--) {
while(greater_eq(d, b, i, lb)) {
for (int j = 0; j < lb; j++) {
d[i + j] -= b[j];
if (d[i + j] < 0) {
// 若可以减,则减
// 这一段是一个高精度减法
d[i + j + 1] -= 1;
d[i + j] += 10;
}
}
// 使商的这一位增加 1
c[i] += 1;
}
}
}
[main]
int a[LEN], b[LEN], c[LEN],d[LEN];
void solve() {
read(a);
read(b);
// mul(a, b, c);
//add(a,b,c);
//sub(a,b,c);
//mul_short(a,b,c);
//div(a,b,c,d);
// print(d);
}
封装大数类
未学。。。。。。
#define MAXN 9999
// MAXN 是一位中最大的数字
#define MAXSIZE 10024
// MAXSIZE 是位数
#define DLEN 4
// DLEN 记录压几位
struct Big {
int a[MAXSIZE], len;
bool flag; // 标记符号'-'
Big() {
len = 1;
memset(a, 0, sizeof a);
flag = 0;
}
Big(const int);
Big(const char*);
Big(const Big&);
Big& operator=(const Big&);
Big operator+(const Big&) const;
Big operator-(const Big&) const;
Big operator*(const Big&) const;
Big operator/(const int&) const;
// TODO: Big / Big;
Big operator^(const int&) const;
// TODO: Big ^ Big;
// TODO: Big 位运算;
int operator%(const int&) const;
// TODO: Big ^ Big;
bool operator<(const Big&) const;
bool operator<(const int& t) const;
void print() const;
};
Big::Big(const int b) {
int c, d = b;
len = 0;
// memset(a,0,sizeof a);
CLR(a);
while (d > MAXN) {
c = d - (d / (MAXN + 1) * (MAXN + 1));
d = d / (MAXN + 1);
a[len++] = c;
}
a[len++] = d;
}
Big::Big(const char* s) {
int t, k, index, l;
CLR(a);
l = strlen(s);
len = l / DLEN;
if (l % DLEN) ++len;
index = 0;
for (int i = l - 1; i >= 0; i -= DLEN) {
t = 0;
k = i - DLEN + 1;
if (k < 0) k = 0;
g(j, k, i) t = t * 10 + s[j] - '0';
a[index++] = t;
}
}
Big::Big(const Big& T) : len(T.len) {
CLR(a);
f(i, 0, len) a[i] = T.a[i];
// TODO:重载此处?
}
Big& Big::operator=(const Big& T) {
CLR(a);
len = T.len;
f(i, 0, len) a[i] = T.a[i];
return *this;
}
Big Big::operator+(const Big& T) const {
Big t(*this);
int big = len;
if (T.len > len) big = T.len;
f(i, 0, big) {
t.a[i] += T.a[i];
if (t.a[i] > MAXN) {
++t.a[i + 1];
t.a[i] -= MAXN + 1;
}
}
if (t.a[big])
t.len = big + 1;
else
t.len = big;
return t;
}
Big Big::operator-(const Big& T) const {
int big;
bool ctf;
Big t1, t2;
if (*this < T) {
t1 = T;
t2 = *this;
ctf = 1;
} else {
t1 = *this;
t2 = T;
ctf = 0;
}
big = t1.len;
int j = 0;
f(i, 0, big) {
if (t1.a[i] < t2.a[i]) {
j = i + 1;
while (t1.a[j] == 0) ++j;
--t1.a[j--];
// WTF?
while (j > i) t1.a[j--] += MAXN;
t1.a[i] += MAXN + 1 - t2.a[i];
} else
t1.a[i] -= t2.a[i];
}
t1.len = big;
while (t1.len > 1 && t1.a[t1.len - 1] == 0) {
--t1.len;
--big;
}
if (ctf) t1.a[big - 1] = -t1.a[big - 1];
return t1;
}
Big Big::operator*(const Big& T) const {
Big res;
int up;
int te, tee;
f(i, 0, len) {
up = 0;
f(j, 0, T.len) {
te = a[i] * T.a[j] + res.a[i + j] + up;
if (te > MAXN) {
tee = te - te / (MAXN + 1) * (MAXN + 1);
up = te / (MAXN + 1);
res.a[i + j] = tee;
} else {
up = 0;
res.a[i + j] = te;
}
}
if (up) res.a[i + T.len] = up;
}
res.len = len + T.len;
while (res.len > 1 && res.a[res.len - 1] == 0) --res.len;
return res;
}
Big Big::operator/(const int& b) const {
Big res;
int down = 0;
gd(i, len - 1, 0) {
res.a[i] = (a[i] + down * (MAXN + 1)) / b;
down = a[i] + down * (MAXN + 1) - res.a[i] * b;
}
res.len = len;
while (res.len > 1 && res.a[res.len - 1] == 0) --res.len;
return res;
}
int Big::operator%(const int& b) const {
int d = 0;
gd(i, len - 1, 0) d = (d * (MAXN + 1) % b + a[i]) % b;
return d;
}
Big Big::operator^(const int& n) const {
Big t(n), res(1);
int y = n;
while (y) {
if (y & 1) res = res * t;
t = t * t;
y >>= 1;
}
return res;
}
bool Big::operator<(const Big& T) const {
int ln;
if (len < T.len) return 233;
if (len == T.len) {
ln = len - 1;
while (ln >= 0 && a[ln] == T.a[ln]) --ln;
if (ln >= 0 && a[ln] < T.a[ln]) return 233;
return 0;
}
return 0;
}
bool Big::operator<(const int& t) const {
Big tee(t);
return *this < tee;
}
void Big::print() const {
printf("%d", a[len - 1]);
gd(i, len - 2, 0) { printf("%04d", a[i]); }
}
void print(const Big& s) {
int len = s.len;
printf("%d", s.a[len - 1]);
gd(i, len - 2, 0) { printf("%04d", s.a[i]); }
}
char s[100024];