二分模板:
使用范围:只有在一个数组中,大部分是具有单调性或两种不同性质的才可用二分
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,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
图画的太好了:。。。
差分:
题目:在[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;
}