785. 快速排序
给定你一个长度为 nn 的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 nn。
第二行包含 nn 个整数(所有整数均在 1∼1091∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 nn 个整数,表示排好序的数列。
数据范围
1≤n≤1000001≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int q[N];
void quick_sort(int q[], int l, int r)
{
if(l >= r) return;
int x = q[(l + r + 1) >> 1];
int i = l - 1;
int j = 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, i-1);
quick_sort(q, i, r);
}
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
quick_sort(q, 0, n - 1);
for (int i = 0; i < n; i ++) printf("%d ", q[i]);
return 0;
}
786. 第k个数
给定一个长度为 nn 的整数数列,以及一个整数 kk,请用快速选择算法求出数列从小到大排序后的第 kk 个数。
输入格式
第一行包含两个整数 nn 和 kk。
第二行包含 nn 个整数(所有整数均在 1∼1091∼109 范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 kk 小数。
数据范围
1≤n≤1000001≤n≤100000,
1≤k≤n1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
//快选算法,O(n)
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k;
int q[N];
int quick_sort(int l, int r, int k)
{
if(l == r) return q[l];
int x = q[l], i = l - 1, j = r + 1; //该处定界时 x=q[l] 与python中不同的
while(i < j)
{
while(q[++ i] < x);
while(q[-- j] > x);
if(i < j) swap(q[i], q[j]);
}
int sl = j - l + 1;
if(k <= sl)
return quick_sort(l, j, k);
return quick_sort(j+1, r, k - sl);
//c++快选中:x = q[l] 与(l, i-1, k)(i, r, k - sl)、(l, j, k)(j+1, r, k - sl) 配套
// x = q[r] 均不可配套
// x = q[(l+r+1)/2] 均不可配套
}
int main()
{
scanf("%d%d", &n, &k);
//cin>>a>>b
for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
printf("%d", quick_sort(0, n - 1, k));
}
//c++快排中:x = q[l] 仅与(q, l, j)(q, j+1, r) 配套 √
x = q[r] 仅与(q, l, i-1)(q, i, r) 配套
x = q[(l+r+1)/2] 均可配套
//c++快选中:x = q[l] 与(l, i-1, k)(i, r, k - sl)、(l, j, k)(j+1, r, k - sl) √ 配套
x = q[r] 均不可配套
x = q[(l + r + 1) / 2] 均不可配套
//python中解决边界问题:均用 x = q[(l+r+1) >> 1]
787. 归并排序
给定你一个长度为 nn 的整数数列。
请你使用归并排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 nn。
第二行包含 nn 个整数(所有整数均在 1∼1091∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 nn 个整数,表示排好序的数列。
数据范围
1≤n≤1000001≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
//双指针问题 、分治问题
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int q[N], tmp[N];
void merge_sort(int q[], int l, int r)
{
if(l >= r) return;
int mid = l + r >> 1; //此处 mid = l + r + 1 >> 1 错误
int k = 0, i = l, j = mid + 1;
merge_sort(q, l, mid); //先将左右两部分排序,构造两个有序序列
merge_sort(q, mid + 1, r);
while(i <= mid && j <= r)
{
if(q[i] >= q[j]) tmp[k++] = q[j++];
else tmp[k++] = q[i++];
}
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];
//对每两个序列的排序,l,r都是不一样的c
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++) scanf("%d", &q[i]);
merge_sort(q, 0, n - 1);
for(int i = 0; i < n; i++) printf("%d ", q[i]);
return 0;
}
788. 逆序对的数量
给定一个长度为 nn 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 ii 个和第 jj 个元素,如果满足 i<ji<j 且 a[i]>a[j]a[i]>a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数 nn,表示数列的长度。
第二行包含 nn 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤1000001≤n≤100000
输入样例:
6
2 3 4 5 6 1
输出样例:
5
逆序对出现的情况有三种: 1. L--○------○---MID-------R
2.L-------------MID--○--○---R
3.L--------○----MID---○----R
这三种情况 均可转换为第三种:
L-----i-----MID
MID+1----j---------R
当在归并排序时 遇到q[i] > q[j]的情况:
说明从i到MID都 > q[j] :则此时逆序对的数量 num += mid - i+ 1
这样就能实现在 归并排序中完成逆序对数量的计算
#include <iostream>
using namespace std;
typedef long long LL; //定义long long 数据类型 为LL
const int N = 1e5 + 10;
int n;
int q[N], tmp[N];
LL merge_sort(int q[], int l, int r) //该处使用了long long 数据类型 LL,使用int型 数据过大会出现负数,超出表示范围
{
if (l >= r) return 0;
int mid = (l + r) >> 1;
int i = l, j = mid + 1, k = 0;
LL num = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r); //递归求解num
while(i <= mid && j <= r)
{
if (q[i] > q[j])
{
tmp[k ++] = q[j ++];
num += mid - i + 1; //逆序对数量
}else tmp[k ++] = q[i ++];
}
//扫尾
while(i <= mid) tmp[k ++] = q[i ++];
while(j <= r) tmp[k ++] = q[j ++];
//将tmp[]中的值赋予q[]
for(int i = l, j = 0; i <= r; i ++, j++) q[i] = tmp[j];
return num;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
cout << merge_sort(q, 0, n - 1) << endl;
return 0;
}
789. 数的范围
给定一个按照升序排列的长度为 nn 的整数数组,以及 qq 个查询。
对于每个查询,返回一个元素 kk 的起始位置和终止位置(位置从 00 开始计数)。
如果数组中不存在该元素,则返回 -1 -1
。
输入格式
第一行包含整数 nn 和 qq,表示数组长度和询问个数。
第二行包含 nn 个整数(均在 1∼100001∼10000 范围内),表示完整数组。
接下来 qq 行,每行包含一个整数 kk,表示一个询问元素。
输出格式
共 qq 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1
。
数据范围
1≤n≤1000001≤n≤100000
1≤q≤100001≤q≤10000
1≤k≤100001≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
二分模板:本质是找边界问题
1. int l = 0, r = n - 1; //该处定义左右端点
while(l < r)
{
int mid = (l + r ) >> 1; //与下文r = mid对应
if(二分check条件) r = mid ;
else l = mid + 1 ;
}
2. int l = 0,r = n - 1; //该处定义左右端点
while(l < r)
{
int mid = (l + r + 1 ) >> 1; //与下文l = mid对应,避免死循环
if(二分check条件) l = mid ;
else r = mid - 1;
}
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int main()
{
int n, m;
int q[N];
cin >> n >> m;
for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
while(m --)
{
int x;
scanf("%d", &x);
int l = 0, r = n - 1;
while(l < r)
{
int mid = (l + r ) >> 1;
if(q[mid] >= x) r = mid ;
else l = mid + 1 ;
}
if(q[l] != x) cout << "-1 -1" <<endl;
else
{
cout << l << " ";
int l = 0,r = n - 1;
while(l < r)
{
int mid = (l + r + 1 ) >> 1;
if(q[mid] <= x) l = mid ;
else r = mid - 1;
}
cout << r <<endl;
}
}
return 0;
}
790. 数的三次方根
给定一个浮点数 nn,求它的三次方根。
输入格式
共一行,包含一个浮点数 nn。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 66 位小数。
数据范围
−10000≤n≤10000−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000
//该题中全部用double型数据,用float型错误
//double双精度浮点型数据
#include <iostream>
using namespace std;
double l = -10000, r = 10000;
int main()
{
double x;
cin >> x;
while(r - l > 1e-8)
{
double mid = (l + r)/2;
if(mid*mid*mid >= x) r = mid; //mid的三次方在c++中不能用mid^3表示:该表示为异或
else l = mid;
}
printf("%.6f",r); //输出小数点后六位
return 0;
}
795. 前缀和
输入一个长度为 nn 的整数序列。
接下来再输入 mm 个询问,每个询问输入一对 l,rl,r。
对于每个询问,输出原序列中从第 ll 个数到第 rr 个数的和。
输入格式
第一行包含两个整数 nn 和 mm。
第二行包含 nn 个整数,表示整数数列。
接下来 mm 行,每行包含两个整数 ll 和 rr,表示一个询问的区间范围。
输出格式
共 mm 行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n1≤l≤r≤n,
1≤n,m≤1000001≤n,m≤100000,
−1000≤数列中元素的值≤1000−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int main()
{
int n, m, q[N], s[N];
cin >> n >> m;
int l, r;
for(int i = 1; i <= n; i ++ )
{
scanf("%d", &q[i]);
s[i] = s[i - 1] + q[i];
}
while(m--)
{
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
796. 子矩阵的和
输入一个 nn 行 mm 列的整数矩阵,再输入 qq 个询问,每个询问包含四个整数 x1,y1,x2,y2x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,qn,m,q。
接下来 nn 行,每行包含 mm 个整数,表示整数矩阵。
接下来 qq 行,每行包含四个整数 x1,y1,x2,y2x1,y1,x2,y2,表示一组询问。
输出格式
共 qq 行,每行输出一个询问的结果。
数据范围
1≤n,m≤10001≤n,m≤1000,
1≤q≤2000001≤q≤200000,
1≤x1≤x2≤n1≤x1≤x2≤n,
1≤y1≤y2≤m1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N],s[N][N];
int x1, y1, x2, y2;
int main()
{
cin >> n >> m >> q;
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{ scanf("%d", &a[i][j]);
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
while(q--)
{
cin >> x1 >> y1 >> x2 >> y2;
int num = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
cout << num << endl;
}
return 0;
}
前缀和问题总结:
序列前缀和问题:注意:所有的数组从下标1开始存储
第一步:构造前缀和数组:s[i] = s[i - 1] + q[i]
第二步:计算前缀和:[l,r]之间元素和:s[r] - s[l - 1]
矩阵前缀和问题:注意:所有的数组从下标1开始存储
第一步:构造子矩阵前缀和数组:s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
第二步:计算子矩阵前缀和:s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
797. 差分
输入一个长度为 nn 的整数序列。
接下来输入 mm 个操作,每个操作包含三个整数 l,r,cl,r,c,表示将序列中 [l,r][l,r] 之间的每个数加上 cc。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 nn 和 mm。
第二行包含 nn 个整数,表示整数序列。
接下来 mm 行,每行包含三个整数 l,r,cl,r,c,表示一个操作。
输出格式
共一行,包含 nn 个整数,表示最终序列。
数据范围
1≤n,m≤1000001≤n,m≤100000,
1≤l≤r≤n1≤l≤r≤n,
−1000≤c≤1000−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
3 4 5 3 4 2
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m, l, r, c;
int a[N], b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++)
{
scanf("%d",&a[i]);
insert(i, i, a[i]);
}
while(m --)
{
cin >> l >> r >> c;
insert(l, r, c);
}
for(int i = 1; i <= n; i ++)
{
a[i] = a[i - 1] + b[i];
cout << a[i] << " ";
}
return 0;
}
#差分问题:b[N] 为差分数组
#a1 = b1
#a2 = b1 + b2
#a3 = b1 + b2 + b3
#a4 = b1 + b2 + b3 + b4
#............
#1.构造差分数组: b[l] += c, b[r + 1] -= c
#2.计算操作后的数组: a[i] = a[i - 1] + b[i]
#前缀和问题:s[N]为前缀和矩阵
#s1 = a1
#s2 = a1 + a2
#s3 = a1 + a2 + a3
#s4 = a1 + a2 + a3 + a4
#.......可求[l,r]之间的额元素和
#1.构造前缀和数组:s[i] = s[i - 1] + a[i]
#2.求[l,r]之间元素和:s[r] - s[l - 1]
798. 差分矩阵
输入一个 nn 行 mm 列的整数矩阵,再输入 qq 个操作,每个操作包含五个整数 x1,y1,x2,y2,cx1,y1,x2,y2,c,其中 (x1,y1)(x1,y1) 和 (x2,y2)(x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 cc。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,qn,m,q。
接下来 nn 行,每行包含 mm 个整数,表示整数矩阵。
接下来 qq 行,每行包含 55 个整数 x1,y1,x2,y2,cx1,y1,x2,y2,c,表示一个操作。
输出格式
共 nn 行,每行 mm 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤10001≤n,m≤1000,
1≤q≤1000001≤q≤100000,
1≤x1≤x2≤n1≤x1≤x2≤n,
1≤y1≤y2≤m1≤y1≤y2≤m,
−1000≤c≤1000−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int x1, y1, x2, y2, c;
int [N][N],b[N][N];
//构造差分矩阵
void insert(int x1, int y1, int x2, int y2,int c)
{
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
cin >> n >> m >> q;
for(int i = 1; i <= n; i ++ )
{
for(int j = 1; j <= m; j ++)
{
scanf("%d",&a[i][j]);
insert(i, j, i, j, a[i][j]);
}
}
while(q --)
{
cin >> 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 ++)
{
//子矩阵的和 a[]为b[]的前缀和矩阵,此处的a[]就是前缀和中的s[]
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
printf("%d ",a[i][j]);
}
cout << endl;
}
return 0;
}
矩阵差分问题核心:构造差分数组公式:
b[x1][y1] += c
b[x1][y2 + 1] -= c
b[x2 + 1][y1] -= c
b[x2 + 1][y2 + 1] += c)
问题解决思路:
1.循环输入原矩阵
2.利用insert函数构造插入原差分矩阵
3.加减操作,得到最终差分矩阵
4.利用前缀和公式计算操作后的a[]
差分问题是前缀和问题的延伸:差分数组b[]就是前缀和问题中的原始数组,差分问题中的a[]就是前缀和问题中的s[]
差分问题:a[]是b[]的前缀和(数组、矩阵)(以前缀和问题为基础)
前缀和问题:s[]是原数组的前缀和(序列前缀和,子矩阵的和)
799. 最长连续不重复子序列
给定一个长度为 nn 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 nn。
第二行包含 nn 个整数(均在 0∼1050∼105 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1≤n≤1051≤n≤105
输入样例:
5
1 2 2 3 5
输出样例:
3
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, a[N],s[N];
int main()
{
cin >> n;
int res = 0, j = 0;
for(int i = 0; i < n; i ++)
{
cin >> a[i] ;
s[a[i]] += 1;
while(s[a[i]] > 1) //check条件
{
s[a[j]] -= 1;
j += 1;
}
res = max(res,i - j + 1);
}
cout << res;
return 0;
}
该问题为双指针问题:
判断是否为双指针问题的关键为:i、j是否为单调移动
如本题中:------j--------------i--------------- :i、j一直向右移动满足单调条件
当前序列(一定满足不重复条件)的下一个状态是有新的a[i]加入,就只可以判断a[i]是否在之前满足条件的序列中重复出现的元素,若有则需要将j一直向右移动直到将之前的重复元素移去当前序列即可,右移过程中同时将s[a[j]] -= 1,还原状态。
800. 数组元素的目标和
给定两个升序排序的有序数组 AA 和 BB,以及一个目标值 xx。
数组下标从 00 开始。
请你求出满足 A[i]+B[j]=xA[i]+B[j]=x 的数对 (i,j)(i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,xn,m,x,分别表示 AA 的长度,BB 的长度以及目标值 xx。
第二行包含 nn 个整数,表示数组 AA。
第三行包含 mm 个整数,表示数组 BB。
输出格式
共一行,包含两个整数 ii 和 jj。
数据范围
数组长度不超过 105105。
同一数组内元素各不相同。
1≤数组元素≤1091≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m, x;
int a[N], b[N];
int main()
{
cin >> n >> m >> x;
for(int i = 0; i < n; i ++) cin >> a[i];
for(int j = 0; j < m; j ++) cin >> b[j];
int j = m - 1;
for(int i = 0;i < n; i ++) //双指针算法模板
{
while(j >= 0 && a[i] + b[j] > x) j --;
if(a[i] + b[j] == x) printf("%d %d",i,j);
}
}
本题中:----i >>------------------------------ :i一直向右移动满足单调条件
------------------------<< j------:j一直向左移动满足单调条件
双指针模板:
for(i = 0,j = 0;i<n;i ++)
{
while(j<i && check(i,j)) j++
//每道题目的具体逻辑
}
2816. 判断子序列
给定一个长度为 nn 的整数序列 a1,a2,…,ana1,a2,…,an 以及一个长度为 mm 的整数序列 b1,b2,…,bmb1,b2,…,bm。
请你判断 aa 序列是否为 bb 序列的子序列。
子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5}{a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}{a1,a2,a3,a4,a5} 的一个子序列。
输入格式
第一行包含两个整数 n,mn,m。
第二行包含 nn 个整数,表示 a1,a2,…,ana1,a2,…,an。
第三行包含 mm 个整数,表示 b1,b2,…,bmb1,b2,…,bm。
输出格式
如果 aa 序列是 bb 序列的子序列,输出一行 Yes
。
否则,输出 No
。
数据范围
1≤n≤m≤1051≤n≤m≤105,
−109≤ai,bi≤109−109≤ai,bi≤109
输入样例:
3 5
1 3 5
1 2 3 4 5
输出样例:
Yes
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
for(int j = 0; j < m; j ++) scanf("%d", &b[j]);
int i = 0, j = 0;
while(i < n && j < m)
{
if(a[i] == b[j]) i ++;
j ++;
}
if(i == n) printf("Yes");
else printf("No");
}
本题中:----i >>------------------------------ :i一直向右移动满足单调条件
------------------------ j >>----------:j一直向右移动满足单调条件
801. 二进制中1的个数
给定一个长度为 nn 的数列,请你求出数列中每个数的二进制表示中 11 的个数。
输入格式
第一行包含整数 nn。
第二行包含 nn 个整数,表示整个数列。
输出格式
共一行,包含 nn 个整数,其中的第 ii 个数表示数列中的第 ii 个数的二进制表示中 11 的个数。
数据范围
1≤n≤1000001≤n≤100000,
0≤数列中元素的值≤1090≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2
#include <iostream>
using namespace std;
int lowbit(int x) //lowbit函数作用为找到x最后一位1
{
return x & -x; //&:位运算中:0&1=0 0&0=0 1&1=1
}
int main()
{
int n, x;
cin >> n;
while(n--)
{
cin >> x;
int res = 0;
while(x)
{
x -= lowbit(x); //减去x的最后一位1
res ++;
}
cout << res << " ";
}
return 0;
}
本题中学习了lowbit函数:return x&-x 的作用,返回x的最后一位
802. 区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 00。
现在,我们首先进行 nn 次操作,每次操作将某一位置 xx 上的数加 cc。
接下来,进行 mm 次询问,每个询问包含两个整数 ll 和 rr,你需要求出在区间 [l,r][l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 nn 和 mm。
接下来 nn 行,每行包含两个整数 xx 和 cc。
再接下来 mm 行,每行包含两个整数 ll 和 rr。
输出格式
共 mm 行,每行输出一个询问中所求的区间内数字和。
数据范围
−109≤x≤109−109≤x≤109,
1≤n,m≤1051≤n,m≤105,
−109≤l≤r≤109−109≤l≤r≤109,
−10000≤c≤10000−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 3e5 + 10;
int n, m, x, c, l, r;
int a[N], s[N];
vector<int> alls;
vector<PII> add, query;
int find(int 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;
}
int main()
{
cin >> n>> m;
for(int i = 0; i < n; i ++) //处理x, c
{
cin >> x >> c;
alls.push_back(x);
add.push_back({x,c});
}
for(int j = 0; j < m; j ++) //处理l, r
{
cin >> l >> r;
alls.push_back(l);
alls.push_back(r);
query.push_back({l,r});
}
//对alls去重,排序
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()),alls.end());
//unique函数找到重复元素的第一个,返回去重后的序列的最后一位+1
//处理插入
for(auto item : add)
{
x = find(item.first);
a[x] += item.second;
}
//预处理前缀和
for(int i = 1; i < alls.size() + 1; i ++) s[i] = s[i - 1] + a[i];
//处理询问
for(auto item : query)
{
l = find(item.first) , r = find(item.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
#为什么 alls加入了l,r并且一起排序:因为alls为下标数组,需要集齐所有出现的下标
#离散化目的:将元素集中在3*10^5内,将范围缩小很多,便于使用前缀和进行计算(a[l],a[r])之间的数据的和
#举例:(x,c):(200,1),(1001,2),(2000,3)
# (l,r):(1,3),(1000,1001),(2010,2200)
#解:定义:a[N]、s[N]、alls、query、add 数组
# ①对于(x,c)的处理:将(x,c)加入alls和add:
# alls:(200,1001,2000) add:({200,1},{1001,2},{2000,3})
# ②对于(l,r)的处理:将(l,r)加入alls和query:
# alls:(200,1001,2000,1,3,1000,1001,2010,2200) query:({1,3},{1000,1001},{2010,2200})
# ③对alls下标数组排序、去重:alls:(1,3,200,1000,1001,2000,2010,2200)
# ④find函数:找到x在alls中的位置:200 --> alls[2] 1001-->alls[4] 2000 -->alls[5]
# ⑤为了便于使用前缀和求解:将求出的位置+1 并将要加的值加到a上:a[2+1] = a[3] = 1 a[4+1] = a[5] = 2 a[5+1]=a[6] = 3
# ⑥此时已经使用alls这个下标数组完成了对离散数据的集中:转换过程:x --> alls[从零开始的某个下标r]=x --> a[r+1]
# ⑦find函数:找到l,r在alls中的位置(同上)
# ⑧前缀和计算即可
# 重点:在整个变换过程中插入元素的相对位置并没有改变,只是元素之间的距离更紧凑了
803. 区间合并
给定 nn 个区间 [li,ri][li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。
输入格式
第一行包含整数 nn。
接下来 nn 行,每行包含两个整数 ll 和 rr。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1≤n≤1000001≤n≤100000,
−109≤li≤ri≤109−109≤li≤ri≤109
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, l, r;
vector<PII> alls;
void merge(vector<PII> &alls)
{
vector<PII> res;
sort(alls.begin(), alls.end());
int start = -2e9, end = -2e9;
for(auto item : alls)
if(end < item.first)
{
if(start != -2e9) res.push_back({start, end});
start = item.first, end = item.second;
}
else end = max(end, item.second);
if (start != -2e9) res.push_back({start,end});
alls = res;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++)
{
cin >> l >> r;
alls.push_back({l, r});
}
merge(alls);
cout << alls.size() << endl;
}
#st ed
#|----------| 当前区间
#① |---| next ∈ 当前区间 ed > next.st ---> ed = max(ed, next.ed)
#② |---------| ed < next.st ---> 当前区间存入res
#③ |---------| ed = next.st ---> ed = max(ed, next.ed) 更新ed
# or |---------| ed > next.st ---> ed = max(ed, next.ed)