基础算法
1. 快速排序-分治
- 确定分界点:第一个,中间,最后一个,随机一个
- 调整区间/重新划分区间
left <= x < right
- 递归处理左右两段区间
快速排序算法模板
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
2. 归并排序-分治
- 确定分界点,
mid = ( left + right ) >> 1
- 递归排序 left、right
- 归并–合二为一 O(n)
双指针算法: 两个有序序列,两个指针指向开头位置,给定一个res数组,两个指针指向的较小者放入到新数组中,然后往后移动,再次比较,小的数放到res中,当其中一个序列结束后,把另外一个序列全部放到res中。
稳定:排序后不发生变化;当两个数相同时,第一个序列的指针往后移动,数字放到res中
输入样例: 1 3 5 7 9
2 4 5 8 10
输出样例:1 2 3 4 5 5 7 8 9 10
归并排序算法模板
void merge_sort(int q[], int l, int r)
{
if (l >= r) return; //判断没有数的情况
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
//归并过程
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
3. 整数二分
二分的本质不是单调性
如果有单调性,一定可以二分,没有单调性,也可能可以二分!
在右半边满足某种性质,左半边不满足这种性质,寻找性质的边界!
-
红色区间
-
寻找中间值
mid = l + r +1 >> 1
判断mid是否满足红色边界点,如果True,mid满足,则mid一定在红色区间,答案在[mid, r],更新方式l = mid
-
如果False, 答案在[l, mid - 1],更新方式
r = mid - 1
-
绿色区间
-
mid = 1 + r >> 1
check, True [1, mid] 更新方式r = mid
- False[mid+1, r] 更新方式
l = mid + 1
整数二分算法模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
选择答案所在的区间进行划分!
二分的时候一定是有解的!
4. 浮点数二分
浮点数不需要处理边界,保证答案在区间内部!
直接从性质判断在哪个区间!不用精度,直接循环100次
4位小数 1e-6
5位小数 1e-7
浮点数二分算法模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
5. 高精度
- 大整数如何存储:
- 输入的时候用字符串表示大整数,然后把大整数的每一位存在数组中,按照逆序,也就是“123456”存在数组里是A[] = [6,5,4,3,2,1],存储的时候选择用vector数组来存。
- 运算模拟人工加法,要用一个变量来判断是否存在进位!
输入并存储大整数
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');
高精度加法
模板1
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
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(t);
return C;
}
模板2
// C = A + B, A >= 0, B >= 0
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;
}
高精度减法
- 输入的时候用字符来输入,但是存储的时候需要用数组来存储,用数组存储的时候注意要把字符转换成数字,也就是每个字符对应的ASCII码减去‘0’;
- 在减法的时候需要比较两个数字的大小,如果是A > B,那么结果是正的,反之就是负的,所以需要判断A B的大小,所以就需要有一个比较函数cmp;
- 在相应的位数上进行计算的时候,需要判断是否借给了前面的位数的数字,也就是是否借1,如果有借的情况,需要减去。因为A > B,所以也要判断B是否存在,当小于B的位数的时候t 会借着减,否则 就要得到结果C的那一位的数值。此时还需要判断两种情况, 也就是A的这一位是否大于B,如果大于就直接得结果,如果小于,就需要向前一位借位,两种情况综合就是(t + 10) % 10,这样就把两种情况都包括在一起了。从 t 的数值可以得出下一位数字, 如果小于0,那么说明借位, t = 1,否则没有借位,就是0
- 讨论前导0的存在:需要判断一下,如果答案是003,因为用数组来存,所以我们只需要打印最后一位的数字!
把大整数分成数组
$A_3 A_2 A_1 A_0$
$B_2 B_1 B_0$
按照逻辑来进行相减,每一位进行减法,分为两个情况:
t 表示借的位数
$A_i$ - $B_i$ + t >= 0 直接减
$A_i$ - $B_i$ + t < 0 需要向前面一位借1, 也就是 $A_i$ - $B_i$ + 10 + t,个位是($A_i$ - $B_i$ + 10 ) % 10
两个整数比较的模板
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;
}
减法模板
// C = A - B, 满足A >= B, A >= 0, B >= 0
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;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
高精度乘法
高精度 X 低精度
- 从题目数据中可以看出是一个很大的整数 乘 一个比较小的数,所以在存储的时候只需要一个就可以了,在使用乘法的时候,也需要用到进位。
- t是计算是把大整数的每一位与小精度的数相乘,求余得到的数字成为新的数字的那一位,剩下的全部进到下一位,并且进位的数字要参与到下一次的运算。
- 最后要处理一下t,因为进位的原因,t可能会存在一些多余的数字,所以要判断是否进位。
- 处理前导0,这是为了防止1234 * 0 ,最终结果只需要输出一个0就可以了。
模板
// C = A * b, A >= 0, b >= 0
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;
}
vector <int> mul(vector <int> & A, int b) {
vector <int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++) {
t += A[i] * b; // t + A[i] * b = 7218
C.push_back(t % 10); // 只取个位 8
t /= 10; // 721 看作 进位
}
while (t) { // 处理最后剩余的 t
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
高精度除法
- 除法和别的不同之处在于需要有一个余数,而且计算的时候是从数组中的最后面开始即一个大整数的最前面开始的,所以我们需要设置一个余数为 r , 存储的时候还是要把最低位存储在数组中的第一个位置,取出来进行计算的时候,也要从最高位开始取数。
- 在计算的时候,每一个的余数需要 * 10,并且加下一位的数字。
- 所得到的结果 C 的结果那一位需要用 我们计算的总余数值 除以 b
- 然后更新r,更新方式为 r = r % b,都需要 从 b 来计算。
- 由于在计算的时候是从高位开始计算的,所以我们需要使用reverse来翻转来可以使用pop方法。最后还是需要去除前导0。
模板
/ A / b = C ... r, A >= 0, b > 0
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;
}
完整代码+注释:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//r传入r的地址,便于直接对余数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 -- )//对A从最高位开始处理
{
r = r * 10 + A[i]; //将上次的余数*10在加上当前位的数字,便是该位需要除的被除数
C.push_back( r / b);//所得即为商在这一位的数字
r = r % b;
}
/*由于在除法运算中,高位到低位运算,因此C的前导零都在vector的前面而不是尾部,
vector只有删除最后一个数字pop_back是常数复杂度,
而对于删除第一位没有相应的库函数可以使用,而且删除第一位,其余位也要前移,
因此我们将C翻转,这样0就位于数组尾部,可以使用pop函数删除前导0*/
reverse(C.begin(), C.end());
while(C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a;
vector<int> A;//a是大数 ,b是小数 A存储a的值, C存储结果的值 r是余数
int b;
cin >> a >> b;
////注意这次的A是由高为传输至低位,由于在除法的手算过程中,发现从高位进行处理
for(int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
int r; // r 代表余数
auto C = div(A, b, r);
//将C从最高位传给最低位
for(int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
//输出余数
cout << endl << r << endl;
return 0;
}
6. 前缀和
什么是前缀和
原数组: a[1], a[2], a[3], a[4], a[5], …, a[n]
前缀和 $S_i$为数组的前 i项和
前缀和: S[i] = a[1] + a[2] + a[3] + … + a[i]
注意: 前缀和的下标一定要从 1开始, 避免进行下标的转换
s[0] = 0
s[1] = a[1]
s[2] = a[1] + a[2]
前缀和的作用
快速求出元素组中某段区间的和
一维数组求解前缀和(Si)
- for循环求出每个S[i] (将 S[0] 定义为 0, 避免下标的转换)
- 求 [l, r]中的和, 即为 S[r] - S[l-1]
模板:一维前缀和
S[i] = a[1] + a[2] + ... a[i]
S[r] - S[l - 1] = a[l] + ... + a[r]
整体代码:
#include <iostream>
using namespace std;
const int N = 100010;// 数组的大小要开足够大
int n,m;
int a[N],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 l,r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r] - s[l-1] );
}
return 0;
}
二维数组求解前缀项和
紫色面积是指(1,1)
左上角到(i,j-1)
右下角的矩形面积, 绿色面积是指(1,1)
左上角到(i-1, j )
右下角的矩形面积。每一个颜色的矩形面积都代表了它所包围元素的和。
接下来回归问题去求以(x1,y1)为左上角和以(x2,y2)为右下角的矩阵的元素的和。
紫色面积是指 ( 1,1 )
左上角到(x1-1,y2)
右下角的矩形面积 ,黄色面积是指(1,1)
左上角到(x2,y1-1)
右下角的矩形面积;
不难推出:
绿色矩形的面积 = 整个外围面积s[x2, y2]
- 黄色面积s[x2, y1 - 1]
- 紫色面积s[x1 - 1, y2]
+ 重复减去的红色面积 s[x1 - 1, y1 - 1]
因此二维前缀和的结论为:
以(x1, y1)
为左上角,(x2, y2)
为右下角的子矩阵的和为:
s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]
总结:
求解s[i] [j]
如图:
求解前缀项和
如图:
7. 差分
一维差分
类似于数学中的求导和积分,差分可以看成前缀和的逆运算。
差分数组:
首先给定一个原数组a
:a[1], a[2], a[3],,,,,, a[n];
然后我们构造一个数组b
:b[1] ,b[2] , b[3],,,,,, b[i];
使得a[i] = b[1] + b[2 ]+ b[3] +..... + b[i]
也就是说,a
数组是b
数组的前缀和数组,反过来我们把b
数组叫做a
数组的差分数组。换句话说,每一个a[i]
都是b
数组中从头开始的一段区间和。
考虑如何构造差分b
数组?
最为直接的方法
如下:
a[0 ]= 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
b[3] =a [3] - a[2];
........
b[n] = a[n] - a[n-1];
图示:
我们只要有b
数组,通过前缀和运算,就可以在O(n)
的时间内得到a
数组 。
知道了差分数组有什么用呢? 别着急,慢慢往下看。
话说有这么一个问题:
给定区间[l ,r ]
,让我们把a
数组中的[ l, r]
区间中的每一个数都加上c
,即 a[l] + c , a[l+1] + c , a[l+2] + c ...... a[r] + c;
暴力做法是for
循环l
到r
区间,时间复杂度O(n)
,如果我们需要对原数组执行``m次这样的操作,时间复杂度就会变成
O(n*m)```。有没有更高效的做法吗? 考虑差分做法。
始终要记得,a
数组是b
数组的前缀和数组,比如对b
数组的b[i]
的修改,会影响到a
数组中从a[i]
及往后的每一个数。
首先让差分b
数组中的b[l] + c
,a
数组变成 a[l] + c ,a[l+1] + c....... a[n] + c;
然后我们打个补丁,b[r+1] - c
, a
数组变成 a[r+1] - c,a[r+2] - c.......a[n] - c;
为啥还要打个补丁?
我们画个图理解一下这个公式的由来:
b[l] + c
,效果使得a
数组中a[l]
及以后的数都加上了c
(红色部分),但我们只要求l
到r
区间加上c
, 因此还需要执行 b[r+1] - c
,让a
数组中a[r+1]
及往后的区间再减去c
(绿色部分),这样对于a[r]
以后区间的数相当于没有发生改变。
因此我们得出一维差分结论:给a
数组中的[ l, r]
区间中的每一个数都加上c
,只需对差分数组b
做b[l] + = c, b[r+1] - = c
。时间复杂度为O(1)
, 大大提高了效率。
总结:
差分模板
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
二维差分
如果扩展到二维,我们需要让二维数组被选中的子矩阵中的每个元素的值加上c
,是否也可以达到O(1)
的时间复杂度。答案是可以的,考虑二维差分。
a
[][]数组是b
[][]数组的前缀和数组,那么b
[][]是a
[][]的差分数组
原数组: a[i][j]
我们去构造差分数组:b[i][j]
使得a
数组中a[i][j]
是b
数组左上角(1,1)
到右下角(i,j)
所包围矩形元素的和。
如何构造b
数组呢?
我们去逆向思考。
同一维差分,我们构造二维差分数组目的是为了 让原二维数组a
中所选中子矩阵中的每一个元素加上c
的操作,可以由O(n*n)
的时间复杂度优化成O(1)
已知原数组a
中被选中的子矩阵为 以(x1,y1)
为左上角,以(x2,y2)
为右下角所围成的矩形区域;
始终要记得,a
数组是b
数组的前缀和数组,比如对b
数组的b[i][j]
的修改,会影响到a
数组中从a[i][j]
及往后的每一个数。
假定我们已经构造好了b
数组,类比一维差分,我们执行以下操作
来使被选中的子矩阵中的每个元素的值加上c
b[x1][y1] += c;
b[x1,][y2+1] -= c;
b[x2+1][y1] -= c;
b[x2+1][y2+1] += c;
每次对b
数组执行以上操作,等价于:
for(int i=x1;i<=x2;i++)
for(int j=y1;j<=y2;j++)
a[i][j]+=c;
我们画个图去理解一下这个过程:
b[x1][y1] +=c
; 对应图1 ,让整个a
数组中蓝色矩形面积的元素都加上了c
。
b[x1,][y2+1]-=c
; 对应图2 ,让整个a
数组中绿色矩形面积的元素再减去c
,使其内元素不发生改变。
b[x2+1][y1]- =c
; 对应图3 ,让整个a
数组中紫色矩形面积的元素再减去c
,使其内元素不发生改变。
b[x2+1][y2+1]+=c
; 对应图4,,让整个a
数组中红色矩形面积的元素再加上c
,红色内的相当于被减了两次,再加上一次c
,才能使其恢复。
我们将上述操作封装成一个插入函数:
void insert(int x1,int y1,int x2,int y2,int c)
{ //对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
我们可以先假想a
数组为空,那么b
数组一开始也为空,但是实际上a
数组并不为空,因此我们每次让以(i,j)为左上角到以(i,j)
为右上角面积内元素(其实就是一个小方格的面积)去插入c=a[i][j]
,等价于原数组a
中(i,j)
到(i,j)
范围内 加上了a[i][j]
,因此执行n*m
次插入操作,就成功构建了差分b
数组.
代码如下:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
insert(i,j,i,j,a[i][j]); //构建差分数组
}
}
总结:
二维差分模板
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
8. 双指针算法
核心思想:将暴力算法O(n*n)
优化到O(n)
朴素做法:
for(int i = 0; i < n; i ++ )
for(int j = 0; j <= i; j ++ )
if(check(j, i))
.............
模板
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
9. 位运算
算法原理
使用lowbit
操作,每次lowbit
操作截取一个数字最后一个1后面的所有位,每次减去lowbit
得到的数字,直到数字减到0,就得到了最终1的个数。
lowbit
原理
根据计算机负数表示的特点,如一个数字原码是10001000
,他的负数表示形势是补码,就是反码+1
,反码是01110111
,加一则是01111000
,二者按位与得到了1000
,就是我们想要的lowbit
操作
模板
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
10. 离散化
离散化的本质,是映射,将间隔很大的点,映射到相邻的数组元素中。减少对空间的需求,也减少计算量。
其实映射最大的难点是前后的映射关系,如何能够将不连续的点映射到连续的数组的下标。此处的解决办法就是开辟额外的数组存放原来的数组下标,或者说下标标志,本文是原来上的数轴上的非连续点的横坐标。
此处的做法是是对原来的数轴下标进行排序,再去重,为什么要去重呢,因为本题提前考虑了前缀和的思想,其实很简单,就是我们需要求出的区间内的和的两端断点不一定有元素,提前加如需要求前缀和的两个端点,有利于我们进行二分搜索,其实二分搜索里面我们一般假定有解的,如果没解的话需要特判,所以提前加入了这些元素,从而导致可能出现重复元素。
模板
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}
完整代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N];
vector<int> alls;// 存储所有待离散化的值
vector<PII> add, query;
// 二分求出x对应的离散化的值
int find(int x)// 找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while(l < r)
{
int mid = l + r >> 1;
if(alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;// 映射到1, 2, ...n
}
vector<int>::iterator unique(vector<int> &a)
{
int j = 0;
for(int i = 0; i < a.size(); i ++ )
if(!i || a[i] != a[i - 1])
a[j ++ ] = a[i];
return a.begin() + j;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++ )
{
int x, c;
cin >> x >> c;
add.push_back({x, c});
alls.push_back(x);
}
for(int i = 0; i < m; i ++)
{
int l, r;
cin >> l >> r;
query.push_back({l, r});
alls.push_back(l);
alls.push_back(r);
}
//去重
sort(alls.begin(), alls.end());// 将所有值排序
alls.erase(unique(alls), alls.end());// 去掉重复元素
//处理插入
for(auto item : add)
{
int x = find(item.first);
a[x] += item.second;
}
//预处理前缀和
for(int i = 1; i <= alls.size(); i ++ )
s[i] = s[i - 1] + a[i];
//处理询问
for(auto item : query)
{
int l = find(item.first), r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
11. 区间合并
首先要对所有的区间进行左端点排序;
然后扫描整个区间,对可能的区间合并:大概分为3个情况,1:存在交集;2:包含关系;3:不存在关系,两区间想隔较远。
思路:
首先用
pair
存储输入的左端点和右端点,然后用sort
对其进行排序,默认是按照左端点进行排序我们需要设置两个大的边界:默认为
-2e9
,,然后对排序后的每个pair
进行操作,1.首先判断当前维护的区间的最右边是否小于下一个区间的左端点,并且这个维护的区间不能是原来的初试区间,我们把这个区间加入到我们的答案,然后更新st 和 ed
,第二种情况是存在交集,那么无论是当前区间的左端点还是下一个区间的左端点,我们根据排序都会选择默认的最小的端点,后面我们只需要选择两个区间的最右边的右端点,那么我们就可以直接维护到这个整的区间,或者说是两个区间合并。- 最后我们需要把最后一个序列放到我们维护的区间里面!当然这个区间也不能是初始区间。
模板
// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
vector<PII> res;
sort(segs.begin(), segs.end());
int st = -2e9, ed = -2e9;
for (auto seg : segs)
if (ed < seg.first)
{
if (st != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
}
else ed = max(ed, seg.second);
if (st != -2e9) res.push_back({st, ed});
segs = res;
}
很难不爱