文章目录
一. 前缀和
题目1:前缀和
(1) 题目
(2) 答案
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i];
while (m --)
{
int l, r;
cin >> l >> r;
cout << s[r] - s[l] + a[l] << endl;
}
return 0;
}
题目2:子矩阵的和
(1) 题目
(2) 思路
(3) 答案
#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N], s[N][N];
int n, m, q;
int main()
{
cin >> n >> m >> q;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> a[i][j];
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];
while (q --)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
int res = s[x2][y2] - s[x2][y1 - 1]- s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
cout << res << endl;
}
return 0;
}
题目3:差分
(1) 题目
(2) 答案
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int n, m;
int main()
{
cin >> n >> m;
// 输入a[]
for (int i = 1; i <= n; i ++) cin >> a[i];
// 找到a[]对应的差分数组b[]
for (int i = 1; i <= n; i ++) b[i] = a[i] - a[i - 1];
// 对于a[]下标为l~r区间上每个数 + c,
// 就等价于,在差分数组b[l] += c, b[r + 1] -= c,
// 最后b[]累加起来就是a[]
while (m --)
{
int l, r, c;
cin >> l >> r >> c;
b[l] += c;
b[r + 1] -= c;
}
for (int i = 1; i <= n; i ++) a[i] = a[i - 1] + b[i];
for (int i = 1; i <= n; i ++) cout << a[i] << ' ';
return 0;
}
题目4:差分矩阵
(1) 题目
(2) 答案
#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N], s[N][N];
int n, m, q;
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()
{
cin >> n >> m >> q;
// a[]为原矩阵
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> a[i][j];
// 1. 找到a[]对应的差分矩阵b[]
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
insert(i, j, i, j, a[i][j]);
// 输出差分矩阵b[]
// for (int i = 1; i <= n; i ++)
// {
// for (int j = 1; j <= m; j ++) cout << b[i][j] << ' ';
// puts("");
// }
// puts("");
// 2. 对差分矩阵b[]进行加加减减操作
while (q --)
{
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
// 3. 对差分矩阵b[]求和得到s[]矩阵
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + b[i][j];
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= m; j ++) cout << s[i][j] << ' ';
puts("");
}
return 0;
}
二. 双指针算法(难)
题目1:最长连续不重复子序列
(1) 题目
(2) 答案
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N]; // s[]统计的是下标从j~i的元素出现的次数
int n;
int main()
{
cin >> n;
for (int i = 0; i < n; i ++) cin >> a[i];
int res = 0;
for (int i = 0, j = 0; i < n; i ++)
{
s[a[i]] ++; // 元素a[i]出现的次数++
// 如果从下标j~i的数组中还有重复元素a[i]
// 那就移动j往前且将a[j]--
// (因为j前进时,a[j]已经不在下标为j~i对应的元素段了)
while (j <= i && s[a[i]] > 1)
{
s[a[j]] --;
j ++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
题目2:数组元素的目标和
(1) 题目
(2) 答案
题目3:判断子序列
(1) 题目
(2) 答案
三. 位运算
题目:二进制中1的个数
(1) 题目
(2) 答案
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> _10to2(int x)
{
vector<int> num;
while (x)
{
num.push_back(x % 2);
x /= 2;
}
return num;
}
int main()
{
cin >> n;
while (n --)
{
int x;
cin >> x;
vector<int> num = _10to2(x);
int res = 0;
for (auto item : num)
if (item) res ++;
cout << res << ' ';
}
return 0;
}
四. 离散化(难)
题目:区间和
(1) 题目
(2) 解析
一段有意义的话:
离散化就是把大而分散的一段段使用到的稀疏区间,整合映射到连续的一段较小的稠密区间里,然后就可以通过普通前缀和公式来计算连续一段的区间和,本质上就是化大为小,把稀疏离散化简为稠密连续的一段。
主要步骤:
- 将要访问的
下标x
全部存到alls[]
中,然后对alls[]
中所有的元素进行排序、去重。
那么!!!在这样一个有序(递增)数组中,对于离散的下标x
可以采用二分法
得到连续的下标i
; - 先将要添加的元素
(x, c)
先存到add[]
数组中,然后把这里面的元素插入到全新的数组a[]
中;
注意:对于下标x
,值为c
的元素:下标x
要做个映射(二分法)到i
假如说映射函数(二分法函数)为get_i
,则i = get_i(x); a[i] += c
- 现有数组为
a[]
,前缀和数组为s[]
,求l~r
的元素和即为s[r] - s[l - 1]
;
注意:l
与r
都是alls[]
中的元素,要映射成a[]
中的下标
(3) 答案
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 3e5 + 10;
typedef pair<int, int> PII;
vector<PII> add, query;
vector<int> alls;
int a[N], s[N];
int n, m;
int get_i(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 l + 1;
}
int main()
{
cin >> n >> m;
/* 个人理解:
区间和--离散化
离散化就是把大而分散的一段段使用到的稀疏区间,
整合映射到连续的一段较小的稠密区间里,
然后就可以通过普通前缀和公式来计算连续一段的区间和,
本质上就是化大为小,把稀疏离散化简为稠密连续的一段。
*/
// 0. 将区间端点x及其上需要添加的数字c存到add中,将x加入到alls中
while (n --)
{
int x, c;
cin >> x >> c;
add.push_back({x, c});
alls.push_back(x);
}
// 0. 将访问的数组区间l~r存到query中和alls中
while (m --)
{
int l, r;
cin >> l >> r;
query.push_back({l, r});
alls.push_back(l);
alls.push_back(r);
}
// 1. 离散化过程
// (1)将alls数组中用到的下标(区间端点)进行排序,并去重
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
// (2)将之前add数组中的元素(x, c)要添加到a[]中的(i,c)上
// 此处,注意:c不为0时,x是不连贯的,i是连贯的,
// 我们要将alls中分散的下标x映射到a[]的下标i上
for (auto item : add)
{
auto x = item.first, y = item.second;
auto i = get_i(x);
a[i] += y;
}
// 2. 回到了连续区间求前缀和的问题上了
for (int i = 1; i <= alls.size(); i ++) s[i] = s[i - 1] + a[i];
// 3. l~r上的元素之和就为s[r]-s[l-1],但是要记得将原来的l、r做个映射
for (auto item : query)
{
auto x = item.first, y = item.second;
auto l = get_i(x), r = get_i(y);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
五. 区间合并(难)
题目:区间合并
(1) 题目
(2) 答案
答案1(更好理解,但是合并后的区间不好找了 )
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
vector<PII> segs;
int n;
int main()
{
cin >> n;
for (int i = 0; i < n; i ++)
{
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
// 将区间们按照前端点进行排序,以便于下一步进行合并
sort(segs.begin(), segs.end());
int res = n;
for (int i = 1; i < n; i ++)
{
// if 前一个区间和后一个区间有交集
// 将后一个区间更新前后端点,
// 前端点为前一个区间前端点,后端点为max(此区间后端点,前一个区间后端点)
// else 啥也不干
if (segs[i - 1].second >= segs[i].first)
{
res --;
segs[i].second = max(segs[i].second,segs[i - 1].second);
segs[i].first = segs[i - 1].first;
}
}
cout << res << endl;
return 0;
}
答案2(不好理解,但是合并后的区间都可以找到)
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
vector<PII> segs;
int n;
int main()
{
cin >> n;
while (n --)
{
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
sort(segs.begin(), segs.end());
vector<PII> res;
// st和ed分别维护当前区间的前后端点
int st = -2e9, ed = -2e9;
for (auto seg : segs)
{
auto l = seg.first, r = seg.second;
// if枚举区间前(左)端点<=当前区间后端点,则二者有交集,合并
if (l <= ed)
ed = max(r, ed);
// else二者无交集,那就更新当前区间
else
{
st = l;
ed = r;
res.push_back({st, ed});
}
}
cout << res.size() << endl;
// 可以看到合并后具体的区间
// for (auto r : res)
// cout << r.first << ' ' << r.second << endl;
return 0;
}