目录
前缀和与差分
前缀和
输入一个长度为n的整数序列
接下来再输入 m个询问,每个询问输入一对l,r
对于每个询问,输出原序列中从第 个数到第r个数的和
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列
接下来m行,每行包含两个整数1和r,表示一个询问的区间范围
输出格式
共m行,每行输出一个询问的结果
数据范围
1<l<r<n,
1<n,m < 100000.
-1000 < 数列中元素的值 < 1000输入样例:
5 3 2 1 3 6 4 1 2 1 3 2 4
输出样例:
3 6 10
思路:先把1-n的每个的前缀和用s数组记录下来(前缀和:就是当前这个数以及前面所有数的和),求l - r区间的总和就是s[r] - s[l] + a[l]; (减去s[l]把a[l]也减去了,所以最后要加上a[l])
解题代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int a[N], s[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin >> a[i];
if(i!=1)s[i] = s[i-1]+a[i];//不是第一个数,那就前面的数之和+当前数
}
while(m--){
int l, r;
cin >> l >> r;
cout << s[r] - s[l] + a[l] << "\n";
}
return 0;
}
子矩阵的和
输入一个 n 行 m列的整数矩阵,再输入 q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标
对于每个询问输出子矩阵中所有数的和
输入格式
第一行包含三个整数n,m,q。
接下来n行,每行包含 m个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1, x2, y2,表示一组询问
输出格式
共q 行,每行输出一个询问的结果
数据范围
1 <= n,m < =1000
1<= q <= 200000
1 <= x1 <= x2 <= n,
1 <= y1 <= y2 <= m;
-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 <bits/stdc++.h>
using namespace std;
int a[1010][1010], s[1010][1010];
int main()
{
int n, m, q;
cin >> n >> m >> q;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
if(i==1 && j== 1)s[i][j] = a[i][j];//第1行第1列
else if(i==1)s[i][j] = s[i][j-1] + a[i][j];//第1行
else if(j==1)s[i][j] = s[i-1][j] + a[i][j];//第1列
else{
//见图1
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
}
}
while(q--){
int x1, x2, y1, y2;
cin >> x1 >> y1 >> x2 >> y2;
//见图2
cout << (s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]) <<"\n";
}
return 0;
}
差分
输入一个长度为 n的整数序列
接下来输入 m 个操作,每个操作包含三个整数l,r,c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来 m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
数据范围
1 <= n,m <= 100000.
1<= l <= r <= n,
-1000 <= c <= 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
思路:a为原数组,题意为在[l,r]区间每个元素+x,用b数组在l位置+x, r+1位置-x,前缀和之后就是题意的意思,现在先m次操作,在b标记,然后再用s数组来前缀和,s数组的元素就是原数组每个元素需要改变的值,将s数组与原数组a相加,之后得到的元素就是改变后的元素。
解题代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int a[N], b[N], s[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
while(m--){
int l, r , x;
cin >> l >> r >> x;
b[l] += x; b[r+1] -= x;
}
for(int i = 1; i <= n; i++)s[i] = s[i-1] + b[i];
for(int i = 1; i <= n; i++){
cout << (s[i] + a[i]) << " ";
}
return 0;
}
差分矩阵
输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数 x1, y1, x2, y2, c,其中(x1,y1)和(x2,y2)表示一个子矩阵的左上角坐标和右下角坐标
每个操作都要将选中的子矩阵中的每个元素的值加上c。
请你将进行完所有操作后的矩阵输出
输入格式
第一行包含整数n,m, q
接下来n行,每行包含m个整数,表示整数矩阵
接下来 q 行,每行包含 5 个整数 x1, y1, x2, y2, c,表示一个操作。
输出格式
共n行,每行m个整数,表示所有操作进行完毕后的最终矩阵
数据范围
1 ≤ n,m ≤ 1000
1≤ 4 ≤ 100000
1 ≤ x1 ≤ x2 ≤ n,
1 ≤ y1 ≤ y2 ≤ m,
-1000 ≤ c≤ 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
思路:差分矩阵与差分类似,先逐行差分(将二维数组化为一维),每行都标记b数组,并进行前缀和,求出每个元素要改变的值,最后与原数组相加,得到改变后的数组。
解题代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N], s[N][N];
int main()
{
int n, m, q;
cin >> n >> m >> q;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
}
}
while(q--){
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
for(int i = x1; i <= x2; i++){//在x1 ~ x2每行都标记b数组
b[i][y1] += c;
b[i][y2 + 1] -= c;
}
}
for(int i = 1; i <= n; i++){//对每一行都前缀和,求每个元素要改变的值
for(int j = 1; j <= m; j++){
s[i][j] = s[i][j-1] + b[i][j];
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cout << s[i][j] + a[i][j] << " ";//原数组+改变值 = 现数组
}
cout << "\n";
}
return 0;
}
双指针算法
最长连续不重复子序列
给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数n。
第二行包含n个整数(均在0~ 10^5范内),表示整数序列
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1 ≤ n ≤ 10^5
输入样例:5 1 2 2 3 5
输出样例:
3
思路:构造以a[i]为结尾的连续不重复区间,遍历a[i],如果s[a[i]] > 1,说明a[i]重复了,用另一个指针 j, 从头开始,走一步这个区间就删掉一个数,知道把重复的a[i]删掉,剩下的就是不重复且连续的,每次操作取最大值,最终得到最长连续不重复区间。
tips: 双指针的核心思想是把O(N^2)的复杂度降为O(N)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)cin >> a[i];
int mx = 0;
//构造以 a[i]为结尾的最长连续不重复区间
for(int i = 0, j = 0; i < n; i++){
s[a[i]]++;//记录数组中a[i]出现的次数
//从头开始,把前面的元素一个一个排掉,直到排到与a[i]重复的
while(j < i && s[a[i]] > 1)s[a[j++]]--;
mx = max(mx, i - j + 1);//最大值
}
cout << mx;
return 0;
}
数组元素的目标和
给定两个升序排序的有序数组 A和B,以及一个目标值
数组下标从0开始
请你求出满足A[i]+B[j]= x的数对(i, j)
数据保证有唯一解.
输入格式
第一行包含三个整数 n,m,x,分别表示A的长度,B的长度以及目标值 x。
第二行包含n个整数,表示数组A
第三行包含m个整数,表示数组B
输出格式
共一行,包含两个整数i和j
数据范围
数组长度不超过10^5
同一数组内元素各不相同
1<= 数组元素 <= 10^9
输入样例:4 5 6 1 2 4 7 3 4 6 8 9
输出样例:
1 1
思路:
方法1:通过遍历a数组,再通过二分找到要达到目标值的b数组元素。
方法2:双指针,i 指针从前往后遍历 a数组, j指针从后往前遍历b数组
方法3:直接暴力枚举,复杂度 O(N^2),这里就不写代码了
解题代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int n, m, x;
//遍历 + 二分
void solve1()
{
for(int i = 0; i < n; i++){
//二分找b[j]
int l = 0, r = m -1;
while(l < r){
int mid = (l + r + 1) >> 1;
if(b[mid] + a[i] > x){//两个数的和超过了x,第二个数往小的找
r = mid - 1;
}
else{
l = mid;
}
}
if(a[i] + b[l]==x){
cout << i <<" " << l <<"\n";
break;
}
}
}
//双指针
void solve2()
{
int i = 0, j = m-1;
for(; i < n; i++){
while(j >= 0 && b[j] + a[i] > x)j--;
if(a[i]+b[j]==x)break;
}
cout << i << " " << j;
}
int main()
{
cin >> n >> m >> x;
for(int i = 0; i < n; i++)cin >> a[i];
for(int i = 0; i < m; i++)cin >> b[i];
// solve1();
solve2();
return 0;
}
判断子序列
给定一个长度为n的整数序列 a1,a2...,an 以及一个长度为 m的整数序列 b1,b2,..·.,bm。
请你判断 a序列是否为b序列的子序列
子序列指序列的一部分项按原有次序排列而得的序列,例如序列[a1,a3,a5]是序列{a1,a2,a3,a4,a5}的一个子序列。
输入格式
第一行包含两个整数n,m。
第二行包含n 个整数,表示 a1,a2,..·,an。
第三行包含 m 个整数,表示 bl,b2,...,bm。
输出格式
如果a序列是6序列的子序列,输出一行 Yes
否则,输出 Noo
数据范围
1≤n≤m≤10^5,
−10^9≤ai,bi≤10^9输入样例:
3 5 1 3 5 1 2 3 4 5
输出样例:
Yes
思路:用两个指针,i 在a数组里面找,j 在b数组里面找;如果数组a是b的子序列,它的元素出现的次序也和在b出现的次序一样,当a数组的元素当前指针 i 在b中当前指针 j 匹配 就往后找a数组下下一个元素;不匹配,指针j就要往后找;当a数组的每个元素都匹配到了,则a序列是b序列的子序列。
解题代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 0; i < n; i++)cin >> a[i];
for(int i = 0; i < m; i++)cin >> b[i];
int i = 0, j = 0;
for(; j < m && i < n;){
if(a[i]==b[j]){//相等两个指针都往后走
i++; j++;
}else{//j往b数组后面找
j++;
}
}
if(i == n)//a数组每个数都能在b找到
puts("Yes");
else
puts("No");
return 0;
}
位运算
求 n 的第 k 位数字: n >> k & 1; 返回n的最后一位1: lowbit(n) = n & -n;
将n二进制的最低位1移除:n & (n - 1);
0 & 0 = 0; 0 & 1 = 0; 1 & 1 = 1;
0 | 0 = 0; 0 | 1 = 1; 1 | 1 = 1
0 ^ 0 = 0; 0 ^ 1 = 1; 1 ^ 1 = 0
对于任何数:n ^ n = 0; n ^ 0 = n
自反性: a ^ b ^ b = a ^ 0 = a;
二进制中1的个数
给定一个长度为n的数列,请你求出数列中每个数的二进制表示中1的个数。
输入格式
第一行包含整数n。
第二行包含n个整数,表示整个数列。
输出格式
共一行,包含n个整数,其中的第个数表示数列中的第个数的进制表示中1的个数。
数据范围
1 ≤ n ≤ 100000,
0 ≤ 数列中元素的值 ≤ 10^9输入样例:
5 1 2 3 4 5
输出样例:
1 1 2 1 2
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
while(n--)
{
long long x;
int s = 0;
cin >> x;
while(x){
if(x & 1) s ++;//当前元素二进制的最后一位是否为1
x >>= 1;//x 右移一位,相当于 / 2
}
cout << s << " ";
}
return 0;
}
练习题:
离散化
离散化: 把一个值域很大,个数很少的数组 映射 到 较小 的数组里面。即把大而分散的一段稀疏区间,整合映射到连续的较小的稠密区间。
区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是0。现在,我们首先进行n次操作,每次操作将某一位置上的数加c
接下来,进行m次询问,每个询问包含两个整数 l 和r,你需要求出在区间[l, r]之间的所有数的和。
输入格式
第一行包含两个整数n和m。
接下来 n 行,每行包含两个整数a 和c再接下来 m行,每行包含两个整数 l 和r。
输出格式
共m行,每行输出一个询问中所求的区间内数字和。
数据范围
−10^9≤x≤10^9
1≤n,m≤10^5,
−10^9≤l≤r≤10^9,
−10000≤c≤10000
输入样例:3 3 1 2 3 6 7 5 1 3 4 6 7 8
输出样例:
8 0 5
思路:每个下标存起来,新的下标为离散化下标,把每个下标排序去重,插入操作要用离散后的下标进行标记,然后进行前缀和,再进行查询操作; 查询操作中要用下标找到离散后的下标,最后得到结果。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
ll a[N], s[N];
vector<ll> alls;
vector<pair<ll, ll> > add, query;
// 返回输入坐标的离散化下标
ll find(ll x)
{
ll l = 0, r = alls.size() - 1;
while(l < r)
{
ll mid = l + r >> 1;
if(alls[mid] >= x){
r = mid;
}else{
l = mid + 1;
}
}
return r + 1;
}
int main()
{
int n, m;
cin >> n >> m;
for(int i = 0; i < n; i++){
ll x, c;
cin >> x >> c;
alls.push_back(x);//位置下标存进去
add.push_back({x, c});//位置x + c
}
for(int i = 0; i < m; i++){
ll 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.begin(), alls.end()), alls.end());//去重
//插入操作
for(auto e: add){
ll x = find(e.first);//找到下标对应的离散下标
a[x] += e.second;
}
//前缀和
for(int i = 1; i <= alls.size(); i++)s[i] = s[i-1] + a[i];
//查找
for(auto e: query){
ll l = find(e.first);//找到下标的离散下标
ll r = find(e.second);
cout << s[r] - s[l-1] << "\n";
}
return 0;
}
升级版:利用set,map数组,不用再写一个函数找离散化下标
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
ll a[N], s[N];
vector<ll>alls;
vector<pair<ll, ll> > add, query;
int main()
{
int n, m;
cin >> n >> m;
set<ll> sidx;//下标去重+排序
for(int i = 0; i < n; i++){
ll x, c;
cin >> x >> c;
add.push_back({x, c});
sidx.insert(x);
}
for(int i = 0; i < m; i++){
ll l, r;
cin >> l >> r;
query.push_back({l, r});
sidx.insert(l);
sidx.insert(r);
}
map<ll, ll>midx;//每个下标给一个离散下标
ll cnt = 1;
for(auto e: sidx){
midx[e] = cnt++;
}
for(auto e: add){
ll x = midx[e.first];
a[x] += e.second;
}
for(int i = 1; i <= cnt; i++){
s[i] = s[i-1] + a[i];
}
for(auto e: query){
ll l = midx[e.first];
ll r = midx[e.second];
cout << s[r] - s[l-1] << "\n";
}
return 0;
}
区间合并
给定n个区间[li,ri],要求合并所有有交集的区间
注意如果在端点处相交,也算有交集
输出合并完成后的区间个数例如: [1,3]和[2,6]可以合并为一个区间[1,6]
输入格式
第一行包含整数n。
接下来n行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数
数据范围
1≤n≤100000,
−10^9≤li≤ri≤10^9
输入样例:5 1 2 2 4 5 6 7 8 7 9
输出样例:
3
思路: 记录左右端点,对左端点进行排序,记录当前合并区间的右端点(一开始是第一个区间[~, pre])pre,如果左端点比pre大,说明不能和[~, pre]合并,另开一个合并区间res++; 否则和当前合并区间合并,更新合并区间的右端点(注:如果当前i区间的右端点比pre大,说明合并区间要扩大,更新pre,否则不需要扩大合并区间,不用动pre)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<pair<ll, ll> > all;
int main()
{
int n;
cin >> n;
while(n--){
ll l, r;
cin >> l >> r;
all.push_back({l, r});
}
sort(all.begin(), all.end());
ll pre = all[0].second, res = 1;
for(int i = 1; i < all.size(); i++)
{
if(all[i].first > pre){
res ++;
pre = all[i].second;
}
if(all[i].second > pre){
pre = all[i].second;
}
}
cout << res;
return 0;
}
本篇包括了前缀和与差分,双指针算法,位运算,离散化,区间合并算法。每天学一点,离目标更近了一点,加油吧,少年~