第一章 基础算法(二)——高精度,前缀和与差分

高精度运算

两个大数做运算,位数一般是1e6

大整数的存储,数组的低位存储整数的低位,类似于小端存储

高精度加法

Ai + Bi + t:每一位相加,再加上进位,一开始进位为0
t变量保存AiBi相加的结果,不过需要先判断这两个数是否存在,一开始t0
AB的每一位数都加完后,还判断是否还有进位,即t是否为1,若有进位,最高位需要补上1

// C = A + B
vector<int> add(vector<int>& A, vector<int>& B)
{
	vector<int> C;
	int t = 0; // 进位
	for (int i = 0; i < A.size() || i < B.size(); ++i)
	{
		if (i < A.size()) t += A[i];
		if (i < B.size()) t += B[i];
		C.push_back(t % 10);
		t /= 10;
	}
	if (t) C.push_back(1);
	return C;
}

另外一种思路,默认将A作为更长的大整数,也就是当A的长度小于B的程度时,执行add(B, A)

// C = A + B
vector<int> add(vector<int>& A, vector<int>& B)
{
	vector<int> C;
	int t = 0; // 进位
	if (A.size() < B.size()) return add(B, A);
	for (int i = 0; i < A.size(); ++i)
	{
		t += A[i];
		if (i < B.size()) t += B[i];
		C.push_back(t % 10);
		t /= 10;
	}
	if (t) C.push_back(1);
	return C;
}

高精度减法

Ai - Bi - t:从最低位开始,每一位相减,再减去借位
若以上的值小于0,那么计算的结果为Ai - Bi - t + 10
若以上的值大于等于0,那么计算的结果为Ai - Bi - t
将以上的结果保存到t中,也就是每次计算的时候:t = A[i] - B[i] - t
对于最后的结果,可以用(t + 10) % 10处理:将t小于0以及t大于等于0的两种情况合并到一起

以上计算的前提是:A大于B。若A小于B, 则需要计算B - A。即保证被减数的绝对值大于减数

所以在进行计算前,需要比较两个数的大小:

  • 先比较长度,长度长的数更大
  • 若两数长度一样,那么从高位开始比较
    • A i A_i Ai != B i B_i Bi,则返回 A i A_i Ai > B i B_i Bi这个表达式的结果
  • 若循环走完,函数还没有返回,那么返回true,表示AB相等

以下模板只给出了两个正数相减的情况。若数字存在负数,此时可以转换为两数的绝对值相减或者相加,关于最后的符号问题,分情况讨论即可

需要注意的是:最后需要去除前导0

bool cmp(vector<int>& A, vector<int>& B)
{
    if (A.size() != B.size()) return A.size() > B.size();
    for (int i = A.size() - 1; i >= 0; --i)
        if (A[i] != B[i]) return A[i] > B[i];
    return true;
}

// 保证A > B
vector<int> sub(vector<int>& A, vector<int>& B)
{
    int t = 0;
    vector<int> C;
    for (int i = 0; i <= A.size(); ++i)
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    } 
    // 去除前导0
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

高精度乘法

注意:只有一个数是高精度的,另一个数比较小
A * blen(A) <= 10b <= 10000
b看成一个整体和A相乘,而不是Ab一位一位的相乘

t保存每一次b A i A_i Ai相乘的结果,同时加上进位,注意t同时也是进位,且t的初值为0
t += A[i] * bt / 10为下一次计算的进位,t % 10C需要保存的结果,保存的顺序从低位开始到高位

A n A_n An的每一位和b相乘完后,可能还留有进位,即t不为0,此时仍然需要对t进行% 10 / 10的操作,直到t0

可以通过以下具体例子,推演高精度乘法的过程
image.png

// C = A * b
vector<int> mul(vector<int>& A, int b)
{
	vector<int> C;
	int t = 0; // 进位
	
	for (int i = 0; i < A.size() || t; ++i)
	{
		if (i < A.size()) t += A[i] * b;
		C.push_back(t % 10);
		t /= 10;
	}
	while (C.size() > 1 && C.back() == 0) C.pop_back();
	return C;
}

注意:乘法运算结果,也需要去除前导0

高精度除法

同样,A为高精度,而b是较小的数

  1. 从最高位 A i A_i Ai开始,此时余数为0,将余数乘以10,加上 A i A_i Ai,作为新的被除数
  2. 将被除数除以b,商作为结果的第一位(最高位 ),保留被除数模b的余数
  3. 将余数乘以10,加上 A i − 1 A_{i-1} Ai1。得到新的被除数,商作为结果的第二位,保留除数模b的余数…
  4. 直到走到A的最低位,此时的余数为整个结果的余数

可以发现,余数和被除数之间存在着一种转换关系,这里用r表示余数与被除数
由于我们使用数组的低位保存大整数的低位,而除法运算中,我们每次得到的是结果的高位。并且整数的高位到数组的低位上,所以最后需要反转字符串。同时,需要去除前导0

// C = A / b
vector<int> div(vector<int>& A, int b, int &r)
{
	vector<int> C;
	r = 0;
	for (int i = A.size() - 1; i >= 0; --i)
	{
		r = r * 10 + A[i]; // 得到被除数
		C.push_back(r / b);
		r %= b;            // 得到余数
	}
	reverse(C.begin(), C.end());
	while (C.size() > 1 && C.back() == 0) C.pop_back();
	
	return C;
}

前缀和

数组 a n a_n an,前缀和 S i S_i Si就是累加 a 1 a_1 a1 a i a_i ai

如何求Si?
公式: S i S_i Si = S i − 1 S_{i-1} Si1 + a i a_i ai

作用?
快速的求出原数组中某一区间的和,sum[l, r] = S r S_r Sr - S l − 1 S_{l-1} Sl1

二维前缀和

公式: S i j S_{ij} Sij = a i j a_{ij} aij + S i − 1 , j S_{i-1,j} Si1,j + S i , j − 1 S_{i,j-1} Si,j1 - S i − 1 , j − 1 S_{i-1,j-1} Si1,j1


差分

给定数组 a n a_n an,构造 b n b_n bn数组使得 a n a_n an数组是 b n b_n bn数组的前缀和
b 1 b_1 b1 = a 1 a_1 a1 - a 0 a_0 a0
b 2 b_2 b2 = a 2 a_2 a2 - a 1 a_1 a1
b n b_n bn = a n a_n an - a n − 1 a_{n-1} an1,注意 a 0 a_0 a0的值为0,这是假设的
但是构建差分数组不使用以上方法,以上只是差分数组的性质。构建差分数组则是利用这一性质:

若现在有操作需要对[ a l a_l al, a r a_r ar]中的所有数加上c。此时可以不对 a n a_n an进行操作,而对其差分数组 b n b_n bn进行操作

因为 a n a_n an b n b_n bn的前缀和数组,若 b l b_l bl + c,那么 a l a_l al + c, a l + 1 a_{l+1} al+1 + c, a r a_r ar + c。因为 a r + 1 a_{r+1} ar+1不需要+c。因此 b r + 1 b_{r+1} br+1 - c,使得 a r + 1 a_{r+1} ar+1不变。这样操作就只用O(1)的时间完成了需要对 a n a_n an数组的O(n)操作

差分数组的构造,假定原矩阵 a n a_n an元素全为0,然后将 a i a_i ai插入到区间[i, i]中,此时修改 b i b_i bi b i b_i bi + a i a_i ai,修改 b i + 1 b_{i+1} bi+1 b i + 1 b_{i+1} bi+1 - a i a_i ai

对于差分,不需要考虑如何构造,只需要考虑如何更新即可。以上方法是一种特殊的更新,可以用于差分的构造

差分模板:

// 核心是insert操作,对原数组的某段区间加上某个值
void insert(int l, int r, int c)
{
	b[l] += c;
	b[r + 1] -= c;
}

至于原数组 a n a_n an怎么求,对差分数组 b n b_n bn用前缀和公式即可

二维差分

同样,构造即更新,所以先理解更新:
若给定一个二维矩阵,矩阵中ij列的元素用 a i j a_{ij} aij表示,现需要对矩阵中以[ x 1 x_1 x1, y 1 y_1 y1]为左上角,以[ x 2 x_2 x2, y 2 y_2 y2]为右下角中的所有元素加上某个值,要怎么做?

根据 a i j a_{ij} aij矩阵构造其差分矩阵 b i j b_{ij} bij,其中 a i j a_{ij} aij矩阵为 b i j b_{ij} bij矩阵的前缀和。
b x 1 , y 1 b_{x_1, y_1} bx1,y1 + c时, a i j a_{ij} aij矩阵中,满足i >= x 1 x_1 x1 && j >= y 1 y_1 y1的所有元素都变成: a i , j a_{i, j} ai,j + c。
而需要+ c的区间为[ x 1 x_1 x1, y 1 y_1 y1]到[ x 2 x_2 x2, y 2 y_2 y2],所以对于满足i >= x 2 x_2 x2 || j >= y 2 y_2 y2的元素,需要保持原样,现在它们的值为 a i , j a_{i, j} ai,j + c,需要修改为 a i , j a_{i, j} ai,j + c - c
此时需要修改差分数组, b x 2 + 1 , y 1 b_{x_2+1, y_1} bx2+1,y1 - c, b x 1 , y 2 + 1 b_{{x_1}, y_2+1} bx1,y2+1 - c, b x 2 + 1 , y 2 + 1 b_{x_2+1, y_2+1} bx2+1,y2+1 + c
此时该差分矩阵推导出的原矩阵中,以[ x 1 x_1 x1, y 1 y_1 y1]为左上角,以[ x 2 x_2 x2, y 2 y_2 y2]为右下角中的所有元素都 + c

二维差分模板:

void insert(int x1, int y1, int x2, int y2, int c)
{
	b[x1][y1] += c;
	b[x2 + 1][y1] -= c;
	b[x1][y2 + 1] -= c;
	b[x2 + 1][y2 + 1] += c;
}

image.png


高精度练习题

791. 高精度加法

791. 高精度加法 - AcWing题库
image.png

输入的处理:用string获取两个加数AB,将加数的低位放到数组的低下标处,高位放到数组的高下标处

用高精度加法模板进行加法运算

#include <iostream>
#include <string>
#include <vector>
using namespace std;

vector<int> add(vector<int>& A, vector<int>& B)
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size() || i < B.size(); ++i) 
    {
        if (i < A.size()) t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }
    if (t) C.push_back(1);
    
    return C;
}

int main()
{
    string a, b;
    cin >> a >> b;
    vector<int> A, B;
    for (int i = a.size() - 1; i >= 0; --i) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; --i) B.push_back(b[i] - '0');
    
    vector<int> C = add(A, B);
    for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
    return 0;
}

792. 高精度减法

792. 高精度减法 - AcWing题库
image.png
image.png

我认为cmp的判断写得很巧妙,两个判断都是当不等于的时候,返回一个比较的结果
这样子处理的逻辑也更清晰
以及,当A < B时,为了不写花括号,y总甚至写了逗号表达式,怎么说,这样子处理也是不错的吧

#include <iostream>
#include <string>
#include <vector>
using namespace std;

string a, b;
vector<int> A, B;

bool cmp(vector<int>& A, vector<int>& B)
{
    if (A.size() != B.size()) return A.size() > B.size();
    for (int i = A.size() - 1; i >= 0; --i)
        if (A[i] != B[i]) return A[i] > B[i];
    return true;
}

// 保证A > B
vector<int> sub(vector<int>& A, vector<int>& B)
{
    int t = 0;
    vector<int> C;
    for (int i = 0; i <= A.size(); ++i)
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    } 
    // 去除前导0
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

int main()
{
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; --i) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; --i) B.push_back(b[i] - '0');
    vector<int> C;
    if(cmp(A, B)) C = sub(A, B);
    else C = sub(B, A), printf("-");
    
    for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
    
    return 0;
}

793. 高精度乘法

793. 高精度乘法 - AcWing题库
image.png

#include <iostream>
#include <string>
#include <vector>
using namespace std;

string a;
vector<int> A;
int b;

vector<int> mul(vector<int>& A, int b)
{
    int t = 0;
    vector<int> C;
    for (int i = 0; i < A.size() || t; ++i)
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

int main()
{
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; --i) A.push_back(a[i] - '0');
    auto C = mul(A, b);
    for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
    return 0;
}

794. 高精度除法

794. 高精度除法 - AcWing题库
image.png

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;
vector<int> A;
string a;
int b, r;

vector<int> div(vector<int>& A, int b, int& r)
{
    r = 0;
    vector<int> C;
    for (int i = A.size() - 1; i >= 0; --i)
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    
    return C;
}

int main()
{
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; --i) A.push_back(a[i] - '0');
    
    auto C = div(A, b, r);
    for (int i = C.size() - 1; i >= 0; --i) printf("%d", C[i]);
    printf("\n%d", r);
    
    return 0;
}

前缀和练习题

795. 前缀和

795. 前缀和 - AcWing题库
image.png
给定一个数组,获取该数组后,根据其询问的区间返回区间和
利用前缀和数组的预处理,从 a n a_n an数组得到 S n S_n Sn前缀和数组
当询问[l, r]的区间和时,只要返回 S r S_r Sr - S l − 1 S_{l-1} Sl1

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N] = {0}, S[N] = {0};
int n,m;
int l,r;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; ++i) S[i] = S[i - 1] + a[i];
    
    while (m--)
    {
        scanf("%d%d", &l, &r);
        printf("%d\n", S[r] - S[l - 1]);
    }
    
    return 0;
}

796. 子矩阵的和

796. 子矩阵的和 - AcWing题库
image.png

利用二维前缀和的公式即可解题

#include <iostream>
using namespace std;

const int N = 1010;
int a[N][N] = {0}, S[N][N] = {0};
int n, m, q;
int x1, y1, x2, y2;

int main()
{
    scanf("%d%d%d", &n, &m, &q);
    // 获取原矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            scanf("%d", &a[i][j]);
    // 构建前缀和矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            S[i][j] = a[i][j] + S[i - 1][j] + S[i][j - 1] -S[i - 1][j - 1];
            
    // 处理询问
    while (q--)
    {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]);
    }
    return 0;
}

差分练习题

797. 差分

797. 差分 - AcWing题库
image.png

题目输入原数组 a n a_n an,我们用特殊的更新构建其差分数组 b n b_n bn。对于原数组的区间操作,只要修改差分数组即可,最后根据差分数组使用前缀和公式推导原数组即可

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], b[N] = {0};
int n, m;
int l, r, c;

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}

int main()
{
    scanf("%d%d", &n, &m);
    // 获取a数组
    for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
    // 构造其差分数组
    for (int i = 0; i < n; ++i) insert(i, i, a[i]);
    
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        insert(l - 1, r - 1, c);
    }
    
    // 推导a数组
    for (int i = 1; i < n; ++i) b[i] += b[i - 1];
    // 打印最终数组 
    for (int i = 0; i < n; ++i) printf("%d ", b[i]);
    return 0;
}

所以还可以这样解:

#include <iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N] = {0}, b[N] = {0};
int n, m;
int l, r, c;

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}

int main()
{
    scanf("%d%d", &n, &m);
    // 获取a数组
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    // 构造其差分数组
    for (int i = 1; i <= n; ++i) insert(i, i, a[i]);
    
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        insert(l, r, c);
    }
    
    // 推导a数组
    for (int i = 1; i <= n; ++i) a[i] = (b[i] += b[i - 1]);
    // 打印最终数组
    for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
    return 0;
}

其实可以不用创建 a n a_n an数组,直接使用 S n S_n Sn数组即可

#include <iostream>
using namespace std;

const int N = 1010;
int S[N][N] = {0};
int n, m, q;
int x1, y1, x2, y2;

int main()
{
    scanf("%d%d%d", &n, &m, &q);
    // 获取原矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            scanf("%d", &S[i][j]);
    // 构建前缀和矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            S[i][j] += (S[i - 1][j] + S[i][j - 1] -S[i - 1][j - 1]);
            
    // 处理询问
    while (q--)
    {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", S[x2][y2] - S[x2][y1 - 1] - S[x1 - 1][y2] + S[x1 - 1][y1 - 1]);
    }
    return 0;
}

798. 差分矩阵

798. 差分矩阵 - AcWing题库
image.png

#include <iostream>
using namespace std;

const int N = 1010;
int n,m,q;
int x1, y1, x2, y2, c;
int a[N][N] = {0}, b[N][N] = {0};

void insert(int x1, int y1, int x2, int y2, int c)
{
	b[x1][y1] += c;
	b[x2 + 1][y1] -= c;
	b[x1][y2 + 1] -= c;
	b[x2 + 1][y2 + 1] += c;
}

int main()
{
    scanf("%d%d%d", &n, &m, &q);
    
    // 获取a矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            scanf("%d", &a[i][j]);
    
    // 构建其差分矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            insert(i, j, i, j, a[i][j]);
    
    // 根据请求修改差分矩阵 
    while (q--)
    {
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        insert(x1, y1, x2, y2, c);
    }
    
    // 根据差分矩阵与前缀和公式推导原矩阵
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            b[i][j] += (b[i -1][j] + b[i][j - 1] - b[i - 1][j - 1]);
            
    // 输出原矩阵
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
            printf("%d ", b[i][j]);
        printf("\n");
    }
    
    return 0;
}

这里最好将差分数组以及原数组的i = 0以及j = 0的位置初始化为0并且不使用,从下标为1的位置开始使用数组。因为最后推导原数组中,需要用到这些下标为0的元素

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值