二分模板+高精度运算+前缀和与差分(C++)

144 阅读5分钟

二分模板:

使用范围:只有在一个数组中,大部分是具有单调性或两种不同性质的才可用二分

1:答案位于右侧边界(绿色范围内)

此时即寻找第一个满足右侧边界的值(因为right可能为目标值,所以不能让right=mid-1)

更新区间条件为:while(left<right)

mid的计算:mid=left+(right-left)/2;

更新区间方式:if(nums[mid]>=target) right=mid;

                      if(nums[mid]<target) left=mid+1;

2:答案位于左侧边界(红色范围内)

此时即寻找最后一个满足左侧边界的值(因为left可能为目标值,所以不能让left=mid+1)

更新区间条件为:while(left<right)

mid的计算:mid=left+1+(right-left)/2;

更新区间方式:if(nums[mid]>target) right=mid-1;

                      if(nums[mid]=<target) left=mid;

注意:此时在计算mid时需要+1,向上取整,否则就可能会导致死循环

例题解析:

在排序数组中寻找目标值的第一个和最后一个位置

 思路解析:

寻找第一个位置,即代表第一种情况:即在右侧区间

寻找最后一个位置,即代表第二种情况,即在左侧区间

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
    if(nums.empty()) return {-1,-1};
    int left=0;
    int right=nums.size()-1;
    int mid=0;
    while(left<right)
    {
        mid=left+(right-left)/2;
        if(nums[mid]>=target) right=mid;
        else left=mid+1;
    }
    if(nums[left]!=target) return {-1,-1};
    int j=left;
    left=0;
    right=nums.size()-1;
    while(left<right)
    {
        mid=left+1+(right-left)/2;
        if(nums[mid]<=target) left=mid;
        else right=mid-1;
    }
    if(nums[left]!=target) return {-1,-1};
    int k=left;
    return {j,k};
    }
};

高精度加法:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;
const int N = 1e6 + 10;

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++)//只有当i都大于A,B数组的长度时,才结束
	{
		if (i < A.size()) t += A[i];
		if (i < B.size()) t += B[i];
		C.push_back(t % 10);//将t对10取余尾插入答案数组
		t /= 10;//对t进行整除,相当于进位,因为在t+=A[i]时,t此时就可能是0或1
	}
	if (t) C.push_back(1);//如果最后一次的结果t非0,那么t一定是1,所以尾插1和t的效果一样
    //因为个位数相加的最大值是 19(9+9+1),最多只能进位1
	return C;
}

int main()
{
	string a, b;//将a,b先设置为字符串格式,以便随便输入任何长度的数字
	vector<int> A, B;//创建两个数组一个位置存放一个数字

	cin >> a >> b;
	for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');//将字符型转为整形存放在A数组中
	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;
}

高精度减法:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;
const int N = 1e6 + 10;

bool cmp(vector<int>A, vector<int>& B)//对A,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;
}

vector<int> sub(vector<int>& A, vector<int>& B)
{
	vector<int> C;
	for (int i = 0, t = 0; i < A.size(); i++)
	{
		t = A[i] - t;//这里的t作用与加法相同:借位
		if (i<B.size()) t -= B[i];//只有当i在B的范围内才-
		C.push_back((t + 10) % 10);//若两数相减结果为负数,那么向高位借1,也就是10
		if (t < 0) t = 1;/如果两数相减的结果为负数,那么就让t=1,也就是它的上一位会多-1
		else t = 0;//如果两数相减的结果为非负数,那么无借位的情况发生
	}
	while (C.size() > 1 && C.back() == 0) C.pop_back();//由于是数字的前端放在数组后面,所以如果数组的后面为0,那么即可删除那个毫无意义的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');//与加法同理

	if (cmp(A, B))//对A,B进行比较,让大的数减去小的数
	{
		auto C = sub(A, B);
		for (int i = C.size() - 1; i >= 0; i--) printf("%d ", C[i]);//逆序打印与加法同理
	}
	else
	{
		auto C = sub(B, A);
		{
			for (int i = C.size() - 1; i >= 0; i--) printf("%d ", C[i]);
		}
	}

	return 0;
}

高精度乘法:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;

vector<int> mul(vector<int>& A, int b)
{
	vector<int> C;
	int t = 0;
	for (int i = 0; i < A.size(); i++)
	{
		if (i < A.size()) t += A[i] * b;//定义一个t,为的是处理进位情况
		C.push_back(t % 10);//将t%10尾插入数组
		t = t / 10;//进位
	}
	return C;
}

int main()
{
	string a;
	int b;//规定是A[i]的每一位数与b相乘

	cin >> a >> b;
	vector<int>A;
	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;
}

高精度除法:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
//r为余数
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];//上一位的余数*10+新一个余数
		C.push_back(r / b);//取余数整除除数,尾插入数组
		r %= b;//将余数对除数取%,成为新的余数
	}
    //C:00123    真实想要的输出为:123   :为了将前面没有意义的0删除
	reverse(C.begin(), C.end());//由于刚刚做的逆序遍历,所以需要将字符串反转C:32100
	while (C.size() > 1 && C.back() == 0) C.pop_back();//321

	return C;
}

int main()
{
	string a;
	int b;//将a的余数与b相除

	cin >> a >> b;
	vector<int>A;
	for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');

	int r;
	auto C = div(A, b, r);

	for (int i = C.size() - 1; i >= 0; i--) printf("%d ", C[i]);
	cout << endl << r << endl;//123

	return 0;
}

前缀和:

题目:求区间内的[left,right]的和

为什么要有前缀和:为了优化算法

这道题很简单,遍历一遍即可,但是时间复杂度是O(N);

前缀和:用Si储存前ai个数的总和,让,最后让s[right]-s[left-1]即可得到答案

由于只需要s[left-1]和s[right]这两个点,所以时间复杂度仅为:O(1)!

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;

const int N = 10010;
int n, m;
int a[N], int s[N];
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--)
	{
		int left = 0;
		int right = 0;
		scanf("%d%d", &left, &right);
		printf("%d\n", s[right] - s[left - 1]);//结果
	}

	return 0;
}

子矩阵的前缀和:

题目:

输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。

图画的太好了:。。。

基础算法——子矩阵的和_极客少年-CSDN博客_子矩阵的和题目描述输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。算法思想计算矩阵的前缀和:s[x][y] = s[x - 1][y] + s[x][y -1] - s[x -1][y-1] + a[x][y]计算子矩阵的和:s = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - ][y1 -1]代码实现#include &lhttps://blog.csdn.net/qiaoxinwei/article/details/107546968


差分:

题目:在[left,right]区间中加上一个常数c

思路解析:

前缀和:s1=a1  s2=a1+a2  s3=a1+a2+a3  s4=a1+a2+a3+a4

差分:b1=a1  b2=a2-a1  b3=a3-a2  b4=a4-a3

差分相当于前缀和的逆运算

相当于:a1=b1   a2=b1+b2   a3=b1+b2+b3  a4=b1+b2+b3+b4

差分的核心是实现insert函数

在本题中:第一部分调用insert函数:构造差分数列

第二部分调用insert函数,在[left,right]范围内进行+一个常数

为什么可以用差分?

 画图解释:

让差分数组b[left]+=c   , 并让它b[right+1]-=c    注意定义推导式

由于该差分b[left]+=c,那么其之后原数组a,下标left之后的所有值都会加上c

同理由于该差分b[right]+=c,那么其之后原数组a,下标right之后的所有值都会减去c

那么[0,left),(right+1,a.size())并不会受到影响,只有我们的目标区间+c

还原:(注意这里与上面的不同)(因为是自加,所以加的时候自身发生了改变)

b1=b1         =a1+c

b2=b1+b2   =a2+c

b3=b2+b3   =a3+c

b4=b3+b4   =a4+c

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;

const int N = 1010;
int n, m;
int a[N], b[N];

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

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++) insert(i, i, a[i]);

	while (m--)
	{
		int left, right, c;
		scanf("%d%d%d", &left, &right, &c);
		insert(left, right, 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;
}
//b[1]=a[1]
//b[2]=b[2]-a[1]
//b[2]=b[2]+a[2]=a[2]-a[1]
//b[3]=a[3]-a[2]
//b[left]=a[left]-a[left-1]
//b[right]=a[right]-a[right-1]
//b[right+1]=a[right+1]-a[right]

差分矩阵

输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含五个整数x1, y1, x2, y2,c,表示一个子矩阵的左上角坐标和右下角坐标。输出矩阵

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<vector>
#include<algorithm>
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]);

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			insert(i, j, i, j, a[i][j]);

	while (q--)
	{
		int x1, y1, x2, y2, c;
		cin >> x1 >> x2 >> y1 >> 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;
}