https://www.luogu.com.cn/problem/P1932
题目描述:
求 A A A、 B B B的和差积商余!减法运算结果可能带负号!
输入格式:
两个数两行
A
B
输出格式:
五个数
和
差
积
商
余
数据范围:
l e n g t h ( A ) , l e n g t h ( B ) < = 1 0 4 length(A),length(B)<=10^4 length(A),length(B)<=104
A , B > 0 A,B>0 A,B>0
每个点 3 s 3s 3s。
由于是高精度的大整数计算,我们可以采用压位的方式节省空间和时间。存储数字的时候,符号位另外存,其余每 8 8 8个位存进一个数组的项中,并且按照从低位到高位的顺序存储。例如对于数字 7 × 1 0 8 + 28 7\times 10^8+28 7×108+28,存的时候存为 a = [ 2 , 28 , 7 ] a=[2,28,7] a=[2,28,7],其中 a [ 0 ] = 2 a[0]=2 a[0]=2表示后面多少个数字是有效的(即去掉开头 0 0 0之后, a [ 1 : ] a[1:] a[1:]的多少个数字需要考虑)。这样输出的时候,最高位原样输出,剩余位高位补 0 0 0填满 8 8 8位输出(当然输出要逆序)。
对于高精度加法和减法,高精度加法我们可以只实现同号的情形,不同号的情形都可以化为两个非负数相减,这可以放在高精度减法里实现。具体实现就是模拟竖式的加减法即可。
高精度乘法就直接按照一元多项式乘法的方法做即可。
高精度除法可以用倍增优化为减法来做。具体说来,例如我们要算 a / b a/b a/b的商和余数,假设 a = b ( k c 2 c + k c − 1 2 c − 1 + . . . + k 1 2 + k 0 1 ) + r a=b(k_c2^c+k_{c-1}2^{c-1}+...+k_12+k_01)+r a=b(kc2c+kc−12c−1+...+k12+k01)+r,其中商即为 k c 2 c + k c − 1 2 c − 1 + . . . + k 1 2 + k 0 1 , k c = 1 k_c2^c+k_{c-1}2^{c-1}+...+k_12+k_01,k_c=1 kc2c+kc−12c−1+...+k12+k01,kc=1,余数为 r r r,所以我们只需要将 k i k_i ki算出来即可(注意到 k i = 1 , 0 k_i=1,0 ki=1,0)。具体算法即为,先将 b b b倍增到最大的小于等于 a a a的数 b ′ b' b′,这样 b ′ = b 2 c b'=b2^c b′=b2c,我们得到了 a / b a/b a/b的二进制最高位,接着我们将 a − b ′ a-b' a−b′赋值给 a a a,将 b ′ b' b′右移 1 1 1,如果 a > b ‘ a>b‘ a>b‘则 k c − 1 = 1 k_{c-1}=1 kc−1=1并且将 a − b ′ a-b' a−b′赋值给 a a a,否则 k c − 1 = 0 k_{c-1}=0 kc−1=0,不断重复上面的操作。每一步将 k i 2 i k_i2^i ki2i累加到商上,当 b ′ b' b′变成 b b b的时候,商就算出来了,此时 a a a里存的就是余数。
代码如下:
#include
#include
using namespace std;
using ll = long long;
const int N = 1e4 + 10, M = N >> 2;
#define sz 8
// MOD = 10 ^ sz
const ll MOD = 1e8;
char s1[N], s2[N];
// 0为非负,1为负
int sn1, sn2, sn;
ll a[M], b[M], s[M];
ll cp[M], lt[M];
void write(ll num[]);
void clear(ll num[]);
void rm(ll num[]);
void lm(ll num[]);
void cpy(ll num1[], ll num2[]);
int cmp(ll num1[], ll num2[]);
void pls(ll a[], ll b[]);
void mnu(ll a[], ll b[]);
void mul(ll a[], ll b[]);
void div(ll a[], ll b[]);
void write(ll num[]) {
if (sn) putchar('-'), sn = 0;
printf("%lld", num[num[0]]);
for (int i = num[0] - 1; i; i--) printf("%08lld", num[i]);
puts("");
}
// 将num清空变成数字0
void clear(ll num[]) {
for (int i = num[0]; i; i--) num[i] = 0;
num[0] = 1;
}
// num >>= 1
void rm(ll num[]) {
for (int i = num[0]; i; i--) {
if ((num[i] & 1) && i > 1) num[i - 1] += MOD;
num[i] >>= 1;
}
if (!num[num[0]] && num[0] > 1) num[0]--;
}
// num <<= 1
void lm(ll num[]) {
num[0]++;
for (int i = 1; i <= num[0]; i++) {
num[i] <<= 1;
if (i > 1 && num[i - 1] >= MOD) num[i - 1] -= MOD, num[i]++;
}
if (!num[num[0]] && num[0] > 1) num[0]--;
}
// 将num2拷贝到num1上去
void cpy(ll num1[], ll num2[]) {
for (int i = 0; i <= max(num1[0], num2[0]); i++) num1[i] = num2[i];
}
// 比较num1和num2,如果num1 > num2则返回1,<则返回-1,等于返回0
int cmp(ll num1[], ll num2[]) {
if (num1[0] != num2[0]) return num1[0] > num2[0] ? 1 : -1;
for (int i = num1[0]; i; i--)
if (num1[i] != num2[i]) return num1[i] > num2[i] ? 1 : -1;
return 0;
}
// 将s表示的从左向右读的十进制数表示为压8位的从低位到高位读的数
void init(char s[], ll num[]) {
for (int i = strlen(s) - 1; i >= 0; i -= sz) {
ll pw = 1;
num[0]++;
for (int j = i; j > i - sz && j >= 0; j--) {
num[num[0]] += (s[j] - '0') * pw;
pw *= 10;
}
}
}
// 求a + b将答案存进数组s中。如果a和b不同号,则用减法算。
void pls(ll a[], ll b[]) {
// 如果a和b不同号
if (sn1 ^ sn2) {
// 如果a为负,a + b = b - (-a)
if (sn1) sn1 ^= 1, mnu(b, a), sn1 ^= 1;
// 如果b为负,a + b = a - (-b)
if (sn2) sn2 ^= 1, mnu(a, b), sn2 ^= 1;
return;
}
// 如果a和b都是负的,a + b = - ((-a) + (-b))
if (sn1 & sn2) {
sn1 = sn2 = 0, pls(a, b), sn = 1;
// 将符号恢复原状
sn1 = sn2 = 1;
return;
}
clear(s);
s[0] = max(a[0], b[0]);
for (int i = 1; i <= s[0]; i++) {
s[i] += a[i] + b[i];
if (s[i] >= MOD) s[i] -= MOD, s[i + 1]++;
}
if (s[s[0] + 1]) s[0]++;
}
// 求a - b将答案存进数组s中。如果a和b不同号,则用加法算。
void mnu(ll a[], ll b[]) {
// 如果a和b不同号
if (sn1 ^ sn2) {
// 如果a为负,a - b = -(-a + b)
if (sn1) sn1 ^= 1, pls(a, b), sn = 1;
// 如果b为负,a - b = a + (-b)
if (sn2) sn2 ^= 1, pls(a, b);
return;
}
// 如果a和b都是负的,a - b = -b - (-a)
if (sn1 & sn2) {
sn1 = sn2 = 0, mnu(b, a);
sn1 = sn2 = 1;
return;
}
// 如果a和b都是正的,并且a < b,则化为算b - a,符号为负
if (cmp(a, b) == -1) swap(a, b), sn = 1;
// 到此a > b且都为正
clear(s);
s[0] = max(a[0], b[0]);
for (int i = 1; i <= s[0]; i++) {
s[i] += a[i] - b[i];
if (s[i] < 0) s[i] += MOD, s[i + 1]--;
}
// 去掉开头0
while (!s[s[0]] && s[0] > 1) s[0]--;
// 如果a和b交换过,则交换回来
if (sn == 1) swap(a, b);
}
// 求a * b将答案存进数组s中。
void mul(ll a[], ll b[]) {
if (sn1 ^ sn2) sn = 1;
clear(s);
s[0] = a[0] + b[0];
for (int i = 1; i <= a[0]; i++)
for (int j = 1; j <= b[0]; j++) {
s[i + j - 1] += a[i] * b[j];
if (s[i + j - 1] >= MOD)
s[i + j] += s[i + j - 1] / MOD, s[i + j - 1] %= MOD;
}
if (!s[s[0]] && s[0] > 1) s[0]--;
}
void div(ll a[], ll b[]) {
if (sn1 ^ sn2) sn = 1;
// cp存的是2的幂次
clear(cp), cp[1] = 1;
clear(lt);
// 如果a >= b,则将除数倍增到不小于被除数,确定商二进制最高位
while (~cmp(a, b)) lm(b), lm(cp);
// 只要cp存的数不是0,就进行循环
while (cp[0] > 1 || cp[1]) {
if (~cmp(a, b)) {
// a -= b
mnu(a, b), cpy(a, s);
// lt += cp
pls(lt, cp), cpy(lt, s);
}
// b >>= 1, cp >>= 1
rm(b), rm(cp);
}
// 此时,lt是商,a是余数
}
int main() {
scanf("%s%s", s1, s2);
if (s1[0] == '-') s1[0] = '0', sn1 = 1;
if (s2[0] == '-') s2[0] = '0', sn2 = 1;
init(s1, a);
init(s2, b);
clear(s);
pls(a, b);
write(s);
mnu(a, b);
write(s);
mul(a, b);
write(s);
div(a, b);
write(lt);
write(a);
}
预处理时间复杂度 O ( l A + l B ) O(l_A+l_B) O(lA+lB),加减法时间 O ( max { l A , l B } ) O(\max\{l_A,l_B\}) O(max{lA,lB}),乘法时间 O ( l A l B ) O(l_Al_B) O(lAlB),除法时间 O ( ( l A + l B ) log A B ) O((l_A+l_B)\log \frac{A}{B}) O((lA+lB)logBA),空间 O ( l A + l B ) O(l_A+l_B) O(lA+lB)。