Loading

算法

前缀和

一维前缀和

二维前缀和

  • 求前缀和

    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]
    

    例题:AcWing 796. 子矩阵的和 - AcWing

差分

一维差分

针对操作:区间[l, r]加上c,使用差分,可以做到o(1)的时间复杂度。

  • 步骤

    1. 针对a数组,构造一个b数组(构造方法不重要),使得a数组是b数组的前缀和,则b数组称为a数组的差分,a数组称为b数组的前缀和。

    2. 如果要对区间[l, r]加上c,那么对差分b数组中l加上c,对r+1减去c即可。

      // 想要对a数组[L, R]加上c, 则只需要修改b数组两个值即可。
      b[L] += c, b[R + 1] -= c;
      
    3. 计算差分数组的前缀和,此前缀和则为原数组操作后的数组。

      for 1 to n-1
      	b[i] += b[i-1]
      

    Tips: 可将a,b数组都想象成全0的数组,将输入数组a中的每个元素都想象成一次[l,l]区间插入c的操作,则可根据多次插入a数组的元素来构建b数组。

例题:AcWing 795. 前缀和 - AcWing

二维差分

针对操作:将一个子矩阵的数字都加上c

  • 步骤:步骤类似于一维差分。

    1. 假想一个b数组,a数组是b数组的前缀和。

    2. 如果要对以(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
      
    3. 计算差分矩阵的前缀和,此前缀和则为原矩阵操作后的矩阵。

      s[x][y] = s[x-1][y] + s[x][y-1] - s[x1][y1] + b[x][y]
      

例题:AcWing 798. 差分矩阵 - AcWing

双指针

模板:

for(int i = 0, j = 0; i < n; i++)
{
	while(j <= i && check(j, i)) j ++ ;
	res = max(res, i - j + 1);
}

例题:799. 最长连续不重复子序列 - AcWing题库

image-20210904151352510

解题思路:

#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是一直向右移动的。

离散化

适用于处理超大区间内稀疏数据的查询操作。

核心思路:将区间内稀疏的点放在一起并映射成下标。

  1. 区间内稀疏的点包括插入操作的点,查询操作的点。
  2. 将这些点排序后去重,区间内每个点只能对应一个映射。
  3. 查询x的映射值可用二分查找
  4. 查询区间和可用前缀和

例题: 802. 区间和 - AcWing题库

#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. 如果严格小于,则区间数+1
  2. 如果大于等于,则维护的区间变成(st, max(ed, next_seg.ed))

例题:803. 区间合并 - AcWing题库

#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]];}
    
  • 例题:826. 单链表 - AcWing题库

双链表

  • 核心操作

    // 左指针,右指针,下一个位置,值
    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];}
    
  • 例题:827. 双链表 - AcWing题库

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
posted @ 2021-09-09 21:40  c0co  阅读(45)  评论(0)    收藏  举报