算法
前缀和
一维前缀和
-
求前缀和
s[i] = s[i-1] + a[i] -
运算(求区间
[a,b]的和)s[a]-s[b]
二维前缀和
-
求前缀和
s[x][y] = s[x-1][y] + s[x][y-1] - s[x-1][y-1] + a[x][y] -
运算(求子矩阵的和)
// 矩阵坐标左上角为 (x1,y1), 右下角为 (x2,y2). res = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1][y1]
差分
一维差分
针对操作:区间[l, r]加上c,使用差分,可以做到o(1)的时间复杂度。
-
步骤
-
针对a数组,构造一个b数组(构造方法不重要),使得a数组是b数组的前缀和,则b数组称为a数组的差分,a数组称为b数组的前缀和。
-
如果要对区间
[l, r]加上c,那么对差分b数组中l加上c,对r+1减去c即可。// 想要对a数组[L, R]加上c, 则只需要修改b数组两个值即可。 b[L] += c, b[R + 1] -= c; -
计算差分数组的前缀和,此前缀和则为原数组操作后的数组。
for 1 to n-1 b[i] += b[i-1]
Tips: 可将a,b数组都想象成全0的数组,将输入数组a中的每个元素都想象成一次
[l,l]区间插入c的操作,则可根据多次插入a数组的元素来构建b数组。 -
二维差分
针对操作:将一个子矩阵的数字都加上c
-
步骤:步骤类似于一维差分。
-
假想一个b数组,a数组是b数组的前缀和。
-
如果要对以
(x1,y1)为左上角,(x2,y2)为右下角的子矩阵加上c,对差分数组的操作如下。b[x1][y1] += c; // 相当于x1,y1右下角的矩阵都加上了c b[x2+1][y1] -= c; // 相当于x2+1, y1右下角的矩阵都加上c,再减去了c b[x1][y2+1] -= c; // 相当于x2+1, y1右下角的矩阵都加上c,再减去了c b[x2+1][y2+1] += c; // 相当于 x2+1, y2+1右下角的矩阵都加上了c,再减去了2c,再加上了c -
计算差分矩阵的前缀和,此前缀和则为原矩阵操作后的矩阵。
s[x][y] = s[x-1][y] + s[x][y-1] - s[x1][y1] + b[x][y]
-
双指针
模板:
for(int i = 0, j = 0; i < n; i++)
{
while(j <= i && check(j, i)) j ++ ;
res = max(res, i - j + 1);
}

解题思路:
#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5+10;
int a[N], s[N];
using namespace std;
int main()
{
int n ;
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]] ++ ; // i指针向右移动,则a[i]出现的次数+1
while (s[a[i]] > 1) // 如果 a[i] 出现过多次,则说明有重复
{
s[a[j]] -- ; // 此时子序列有重复,则将j右移动,移动前将a[j]的出现次数--
j ++ ; // j右移
}
// 循环结束,[j, i]区间内部没有重复,则长度为i-j+1
res = max(res, i-j+1);
}
cout << res << endl;
return 0;
}
Tips: j从0移动到n-1,并不会回头。假设 区间 [j, i]的子序列没有重复,[j, i+1]的子序列有重复,那么[j-1, i+1]的区间也一定有重复,所以j是一直向右移动的。
离散化
适用于处理超大区间内稀疏数据的查询操作。
核心思路:将区间内稀疏的点放在一起并映射成下标。
- 区间内稀疏的点包括插入操作的点,查询操作的点。
- 将这些点排序后去重,区间内每个点只能对应一个映射。
- 查询x的映射值可用二分查找
- 查询区间和可用前缀和
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
int n, m, x, c, l, r;
const int N = 3e5 + 10;
int a[N], s[N];
vector<PII> add, query;
vector<int> alls;
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 ++ )
{
cin >> x >> c;
add.push_back({x, c});
alls.push_back(x);
}
// 将查询操作进行存储
for (int i = 0; i < m; i ++ )
{
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 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);
int r = find(item.second);
cout << s[r] - s[l-1] << endl;
}
return 0;
}
区间和并
思想:将区间按左端点排序,然后维护一个区间,比较此区间右端点ed与下一个区间左端点st的大小关系。
- 如果严格小于,则区间数+1
- 如果大于等于,则维护的区间变成(st, max(ed, next_seg.ed))
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int n, l, r;
typedef pair<int, int> PII;
vector<PII> segs;
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 (ed != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
}
ed = max(ed, seg.second);
}
if (st != -2e9) res.push_back({st, ed});
segs = res;
}
int main()
{
cin >> n;
while(n -- ){
cin >> l >> r;
segs.push_back({l, r});
}
merge(segs);
cout << segs.size()<<endl;
}
数据结构
链表
单链表
-
核心操作
// 头节点、 值、 next、 当前位置 int head, e[N], ne[N], idx; // 初始化 void init(){head = -1, idx = 0;} // 添加x到头节点 void add_to_head(int x){e[idx] = x, ne[idx] = head, head = idx ++ ;} // 添加x到第k个添加的点后 void add(int k, int x){e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;} // 删除第k个添加的点 void remove(int k){ne[k] = ne[ne[k]];}
双链表
-
核心操作
// 左指针,右指针,下一个位置,值 int l[N], r[N], idx, e[N]; // 一开始左边界节点指向右边界节点,右边界节点指向左边界节点 void init(){r[0] = 1, l[0] = 0, idx = 2;} // 在第k个点右侧添加点x void add(int k, int x){ e[idx] = x; r[idx] = r[k], l[idx] = k; l[r[k]] = idx, r[k] = idx; idx ++ ; } // 删除第k个点 void remove(int k){r[l[k]] = r[k], l[r[k]] = l[k];}
栈
KMP
// 求Next数组
for (int i = 2, j = 0; i <= n; i ++ ){
while(j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= m; i ++ ){
while(j && s[i] != p[j + 1]) j = ne[j]; // 当前位置匹配不成功,j后移
if (s[i] == p[j + 1]) j ++ ; // 当前位置相等,j后移
if (j == n){// 匹配成功}
}
哈希表
存储结构
- 开放寻址法
- 拉链法
字符串哈希方式
经验值:
- 进制P:131
- 模Q:2^64

浙公网安备 33010602011771号