第一章 基础算法(二)包含高精度以及前缀和差分

第一章 基础算法(二)

一、高精度

一般来说,高精度会考有四种:(都是大数)

A + B : A, B位数10^6

A - B : A, B位数10^6

A * a : len(A) <= 10^6 a <= 10^9| a <= 10000(a 这里是指数值,不是位数)

A / a

A * | / B

(1)问题一:大整数在代码中是如何表示的,如何存储?

  • int 变量肯定存储不了大整数;
  • 我们是将大整数的每一位存储在数组里面;
// 例如 123456789, 我们将它放到数组里面
- 假设有一个数组 下标 0 1 2 3 4 5 6 7 8 
    - 我们将 个位放在第0位,即 9 在第0位; 【为什么这样做会比较好呢?】
    - 存完之后 数组下标从0 - 8 位分别对应9 8 7 6 5 4 3 2 1 (将最高位存在了数组最后一个数)
    - 因为我们两个整数相加(运算)会产生进位,那么进位的话我们就需要在高位补上一个数,补上一个数的话,在数组的末尾补上一个数是容易的;在数组的开头补上一个数字的话,就需要将数组所有都往后平移一位,再补,就很麻烦;
    
  • 加减乘除中大整数的存储都是一样的;

(2)运算:模拟人工运算的过程

请添加图片描述

  • A0 + B0 + t (每一位上 ,t = 0 , 1);

(3)ACWING 791 高精度加法

请添加图片描述

#include <bits/stdc++.h>
#include <vector>
using namespace std;


const int N = 1e6 + 10; 

// C = A + B  加 & 是为了提高效率,不然,在调用的时候就会把数组复制一遍, 浪费时间 
vector<int> add (vector<int> &A, vector<int> &B)
{
	vector<int> C;
	
	int t = 0; // 进位, 一开始为0 
	
	// 只要A, B没有走完就一直加
	for (int i = 0; i < A.size() || i < B.size(); ++i)
	{
		// 如果 A 还没有结束, 就加 
		if (i < A.size()) t += A[i];
		// 同理
		if (i < B.size()) t += B[i];
		
		//t =  t + Ai + Bi 进位加上 A,B的每一位 
		
		// 理解一下最后这两步 
		C.push_back(t % 10); // 取模C的当前位, 由“新 ”t 取模 保留个位
		
		t /= 10; //如果t > 10 , t 就要进位 为 1, 否则就是 0 
	}  
	
	// 结束后,如果最高位还有进位的话,就要加 1
	if (t) C.push_back(1);
	return C; 
} 
//用数组表示整数的时候,倒着来表示第0位表示个位,第一位表示十位。。。, 最高位在最后; 
int main ()
{
	string a, b;
	vector<int> A, B;
	
	cin >> a >> b; // 读入两个大整数, 假设 a = "123456"
	// 再将这两个大整数存储到数组中 存储的时候,记得要-'0',变成数,而不是字符 
	for (int i = a.size() - 1; i >= 0; --i) A.push_back(a[i] - '0'); // A = [6, 5, 4, 3, 2, 1]
	for (int i = b.size() - 1; i >= 0; --i) B.push_back(b[i] - '0');
	
	auto C = add (A, B); // auto 编译器会自动推断这个变量是什么类型的 
	
	for (int i = C.size() - 1; i >= 0; --i) printf ("%d", C[i]);
	
	return 0;	
} 

(4)ACWING 792:高精度减法

请添加图片描述

请添加图片描述

  • 通过这样的交换,可以保证我们一定是较大的数,减去较小的数,可以保证最高位一定不会再往前借位的;
  • 比较:同位的话,比较最高位,不行的话,依次往下比;
  • 上一位借过位的话,t 为 1, 否则为 0;

请添加图片描述

#include <bits/stdc++.h>
#include <vector> 
using namespace std;

//如果遇上输入两个,或者其中之一为负整数 
//符号单独处理,计算数值部分不变 ,具体是需要自己去调整的,模板是可以用的 
//但在这里,我们先不考虑负整数的情况 
//判断是否有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]; 
		} 
	}
//	两个数相等也可以 A>=B 返回 true 
	return true; 
} 

vector<int> sub(vector<int> &A, vector<int> &B)
{
	vector<int> C; // 最终结果
	// A 是最大的,循环在A没有走完之前 
	for (int i = 0, t = 0; i < A.size(); ++i)
	{
//		综合式子, t = A[i] - B[i] - t, t是进位 
		t = A[i] - t; 
//		若B还没有越界,则减B[i]
		if (i < B.size()) t -= B[i];
		
		//对当前这位的处理 : 考虑两种情况,当t(t = A[i] - B[i] - t) >= 0  , 当前位就是此t本身,否则,要 + 10 
		//将两种情况合二为一的写
		C.push_back((t + 10) % 10);
		if (t < 0) t = 1; // < 0 说明向当前为借位了,等下要减1
		else t = 0; 
	}	
	// 但是减法还需要注意一点,例如 123 - 120  = 3,但是这样push_back 会变成 003 ,要做对0的处理
//	若是结果只有一位,就算是 0 也不可以去掉 
//	且注意存储也是倒着存的,若是0 0 0 3 0, 在C中就是0 3 0 0 0,所以
	while (C.size() > 1 && C.back() == 0) C.pop_back();  // 去掉前导0 
	return C;
} 
int main ()
{
	string a, b;
	vector<int> A, B;
	
	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');
	
//	比较一下A, B,由大数减小数,可保证最高位一定不会再往前借位
	if (cmp(A, B))
	{
		vector<int> C = sub(A, B);
		for (int i = C.size() - 1; i >= 0; --i) printf ("%d", C[i]);	
	} 
	else 
	{
		vector<int> C = sub(B, A);
		printf ("-");
		for (int i = C.size() - 1; i >= 0; --i) printf ("%d", C[i]); 
	}
	return 0;
}

(5)ACWING 793 高精度乘法

请添加图片描述

请添加图片描述

#include <bits/stdc++.h>
#include <vector>
using namespace std;

vector<int> mul(vector<int> &A, int b) {
	vector<int> C;
	int t = 0; // 进位
	// 当A还有值时时候,以及t还有进位的时候,就继续走循环
	for (int i = 0; i < A.size() || t; ++i) {
		// 按照模拟的算法
		if (i < A.size()) t += A[i] * b;
		C.push_back(t % 10); // 把这一位存储起来
		t /= 10; //保留进位,等下一位要加
	}
	return C;
}
int main() {
	string a; // 输入大整数
	int b;	  // 输入小整数

	cin >> a >> b;

//	注意,如果b为0, 则会有一排0, 对这个特殊数据进行处理
	if (b == 0) printf ("0");
	else {
		vector<int> A;

		for (int i = a.size() - 1; i >= 0; --i) {
			A.push_back(a[i] - '0');
		}

		vector<int> C = mul(A, b);

		for (int i = C.size() - 1; i >= 0; --i) printf ("%d", C[i]);
	}

	return 0;
}
  • 注意 若 b = 0, 这一组数据

(6)ACWING 794 高精度除法

高精度整数 / 低精度整数

请添加图片描述

#include <bits/stdc++.h>
#include <vector>
#include <algorithm> 
using namespace std;

//除法除了返回商外,还会返回余数
//A / b, 商是C, 余数是r 
vector<int> div(vector<int> &A, int b, int &r)
{
	// 注意一下:前面的 + - * 都是从最低位开始,但是除法是从最高位开始算,但是main函数中存储的话,统一倒着存 
	vector<int> C;	
	r = 0; // 余数开始是0
//	余数从最高位开始算起 
	for (int i = A.size() - 1; i >= 0; --i)
	{
		// 从最高位开始算起,然后把个位留出来,再加上这一位的个位 
		r = r * 10 + A[i]; 
//		当前位 商  余数 整除  b 
		C.push_back(r  / b);
//		下一位余数  商完之后的余数 
		r %= b; 
	}
	
//	与前面的不同,算完后,最高位就在前面,再翻转一下(因为在main函数中,统一了输出)
	reverse(C.begin(), C.end());
//	关于前导0要处理一下
	while (C.size() > 1 && C.back() == 0) C.pop_back();  
	return C;
} 
int main() {
	string a; // 输入大整数
	int b;	  // 输入小整数

	cin >> a >> b;

	vector<int> A;

	
	for (int i = a.size() - 1; i >= 0; --i) 
	{
		A.push_back(a[i] - '0');
	}
	int r; //余数 
	vector<int> C = div(A, b, r);

	//输出商 
	for (int i = C.size() - 1; i >= 0; --i) printf ("%d", C[i]); 
	cout << endl;
//	输出余数
	cout << r <<endl; 
	return 0;
}

请添加图片描述

二、 前缀和与差分

若有长度为n的数组, a1, a2, a3…an

前缀和si(元素中前i个数和) = a1 + a2 + a3 +… + ai 【前缀和中,一定要让下标从1 开始】

  • 前缀和是一个公式,思想
1. 如何求si
    s0 = 0;
    for (i = 1; i <= n; ++i) s[i] = s[i - 1] + ai;   


2. 作用
    快速的求出原数组中一段数的和
    例如:求[l, r]
    - 若无前缀和,就要循环一遍数组  O(n)
    - 依靠前缀和,则可以Sr - S(l-1)  ; [l -1]是下标
    - 这样,我们就可以用一次运算,算出任意一段区间的数和;
3. 为什么要让下标从1开始,以及为什么要定义S0?
    - 下标从1开始,定义S0 = 0- 令S0 = 0,为了处理边界
    - 例如:当我们求[1, 10]这段区间的数和,(即求S10)S10 - S0(S0 = 0), 就是为了 统一公式;
	- 通俗一点:如果想求的不是[l, r], 而是[1, x] ,也可以用 Sr - Sl-1 ,这个公式,即Sx - S0,(S0 = 0,无影响);
    - 也因此下标通常从1开始;    

请添加图片描述

(1)ACWING 795. 前缀和

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int a[N];
int s[N];

int n, m, l, r;
int main ()
{
    //ios::sync_with_stdio(false); // 提高cin的读取速度,不能使用scanf
	
    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;
}

(2)如果想快速求出某一个子矩阵的和?也可以用前缀和的思想

请添加图片描述

请添加图片描述

请添加图片描述

  • 画个格子用格子比较好理解

  • 一个点代表一个格子

  • 请添加图片描述

  • 请添加图片描述

(3)ACWING 796. 子矩阵的和

请添加图片描述

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N], s[N][N];
int main()
{
	// first: 输入矩阵规模以及问题个数
	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] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
		}
	}
	int x1, x2, y1, y2;
	while (q--)
	{
		scanf ("%d%d%d%d", &x1, &y1, &x2, &y2);
		printf ("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
	}
	return 0;	
} 

(4)差分其实是前缀和的逆运算

请添加图片描述

  • a 称前缀,b 称差分

  • 对b数组求一遍前缀和,即可得到a数组,O(n);

  • 应用:快速处理,例如:[l, r]区间内,所有的数都加 + c;

  • 请添加图片描述

  • 暴力做法,时间复杂度 O(n), 差分做法 为 O(1);

  • 请添加图片描述

  • 请添加图片描述

  • 可以这么理解:在[1, 1]区间插入 a[1], 在[2, 2]区间插入a[2],…;

  • 请添加图片描述

(5)ACWING 797 差分 :【一维数组差分】

请添加图片描述

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int n, m;
int a[N], b[N]; // 初始全为0 
void insert (int l, int r, int c)
{
	b[l] += c;
	b[r + 1] -= c; 
}
// 插入操作就相当构造了差分数组, 如果初始全为0就很好理解
int main ()
{
	int n, m; // 输入n个序列, m次操作
	scanf ("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
	{
		scanf ("%d", &a[i]);
	}
	// 构造差分 b 数组 
	for (int i = 1; i <= n; ++i)
	{
		insert(i, i, a[i]);
	} 
	int l, r, c;
//	完成m次操作
	while (m--)
	{
		scanf ("%d%d%d", &l, &r, &c);
//		对于区间的修改,时间复杂度就变成了O(1), 而不用遍历数组了
		insert(l, r, c);	
	} 
	
	// 对新形成的差分,重新求前缀和,最后输出即可; 
	for (int i = 1; i <= n; ++i)
	{
		b[i] += b[i - 1];
	}
	for (int i = 1; i <= n; ++i)
	{
		printf ("%d ", b[i]);
	}
	return 0;
} 
  • 差分不需要考虑如何构造,只需要考虑如何更新即可;

(6)二维差分:给子矩阵加上C

请添加图片描述

请添加图片描述

请添加图片描述

(7)ACWING 798 差分矩阵

请添加图片描述

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];
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);
	
	for (int i = 1; i <= n; ++i) 
	{
		for (int j = 1; j <= m; ++j)
		{
			scanf ("%d", &a[i][j]);
		}
	}
	
	//把a矩阵里面的每个数,插到空数组里面  b  
	for (int i = 1; i <= n; ++i) 
	{
		for (int j = 1; j <= m; ++j)
		{
			insert (i, j, i, j, a[i][j]);
		}
	}
	int x1, x2, y1, y2, c;
	// 做q次操作
	while (q--)
	{
		scanf ("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
		insert (x1, y1, x2, y2, c); // 完成+c操作
	} 
	
	// 求所有q次操作更新后的新b数组(差分数组)的前缀和; 
	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]); 
		}
		puts("");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值