1. 基础算法

218 阅读9分钟

1. 基础算法

注:基础算法部分普遍简单,主要看注释,不做过多赘述和解释


1.1 排序

思想:分治

1.1.1 快速排序

步骤分解:

  1. 确定分界点x = q[l]q[r]q[l + r / 2]或随机

  2. 调整范围x,>x\leqslant x, > x

⑴ 开辟额外数组

a[], b[]

② 扫描q[l ~ r]x\leqslant x的部分存入a[]>x> x的部分存入b[]

③ 先将a[]中的元素放入q[],再将b[]中的元素放入q[]

⑵ 双指针

① 指针 ii 指向q[]首元素的前位,指针 jj 指向q[]末元素的后位

② 右移指针 ii ,直到其指向的元素 >x> x

③ 左移指针 jj ,直到其指向的元素 x\leqslant x

④ 交换指针 iijj 所指向的元素,并重复步骤②③,直到 iijj 相遇

  1. 递归处理左右两端
点击查看图片来源
模板
void quick_sort(int q[], int l, int r)// l和r是闭区间边界
{
    // 边界:如果数组内没有数或只有一个数,直接return
    if (l >= r) return ;

    // 确定双指针的初始位置和分界点x
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    // 如果i和j未相遇,则进行下一次迭代
    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, j), quick_sort(q, j + 1, r);
}

1.1.2 归并排序

步骤分解:

  1. 确定分界点——必须取中点mid = (l + r) / 2

  2. 递归排序

  3. 归并——合二为一

① 双指针分别指向两个有序的数列的首元素

② 比较两个指针指向元素的大小,将较小的元素存入新数组,并将该指针前移一位

③ 重复步骤②,直到其中一个指针走到终点,最后将另一个指针后面剩余的部分直接存入新数组

点击查看图片来源
模板
void merge_sort(int q[], int l, int r)// l和r是闭区间边界
{
    // 边界:如果数组内没有数或只有一个数,直接return
    if (l >= r) return;

    // 确定双指针的初始位置和分界点mid,即区间的中点
    int mid = l + r >> 1;
    merge_sort(q, l, mid);// 递归排序左部分
    merge_sort(q, mid + 1, r);// 递归排序右部分

    // 归并排序主体
    int k = 0, i = l, j = mid + 1;// ①
    while (i <= mid && j <= r)// ②
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];

    // ③
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    // 将tmp中的元素存回q
    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}


1.2 二分

1.2.1 整数二分

注意:判断左边界还是右边界,是否需要给边界加一

通用模板
bool check(int x) {/* ... */}// 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while(l < r)
    {
        int mid = l + r >> 1;// 下取整
        if(check(mid)) r = mid;// check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}

// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while(l < r)
    {
        int mid = l + r + 1 >> 1;// 上取整
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
应用模板
// 1.在升序、无重复元素的数组中,查找值为k的元素的数组下标
const int N =  ;

// 在给定数组中查找k值所在位置,若不存在则返回-1
// l为二分下界,r为二分上界
int binarySearch(int a[], int l, int r, int k)
{
    int mid;
    // 此处的循环条件为 l <= r 的原因:
    // 若l > r,即不为闭区间时,作为元素不存在的依据,直接返回-1
    while (l <= r)
    {
        // 此处也可使用 mid = l+(r-l)/2; 语句避免l+r溢出int
        mid = (l + r) / 2;
        if(a[mid] == k) return mid;
        else if(a[mid] > k) r = mid - 1;
        else l = mid + 1;
    }
    return -1;
}

int k, n;
int a[N];
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
scanf("%d", &k);
// 此处传入闭区间[0, n-1]
int pos = binarySearch(a, 0, n-1, k);
printf("%d\n", pos);


// 2.在升序、有重复元素的数组中,查找第一个大于等于k值的元素的数组下标
const int N =  ;

// 在给定数组中查找第一个大于等于k值的元素的数组下标
// 若该元素不存在,则返回假设它存在,应该在的位置
// l为二分下界,r为二分上界
int lowerBoundL(int a[], int l, int r, int k)
{
    while (l < r)
    {
        int mid = (l + r) / 2;
        // 此处是r = mid,这是由判断条件a[mid]有可能等于k决定的
        if(a[mid] >= k) r = mid;
        else l = mid + 1;
    }
    // 此处返回r或l皆可,因为r == l
    return l;
}

int k, n;
int a[N];
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
while(~scanf("%d", &k))
{
    // 此处传入闭区间[0, n]
    // 因为可能数组中所有值都小于k,则返回该元素的应在位置n
    int pos = lowerBoundL(a, 0, n, k);
    printf("%d\n", pos);
}


// 3.在升序、有重复元素的数组中,查找最后一个小于等于k值的元素的数组下标
const int N =  ;

// 在给定数组中查找最后一个小于等于k值的元素的数组下标
// 若该元素不存在,则返回下标0
// l为二分下界,r为二分上界
int lowerBoundR(int a[], int l, int r, int k)
{
    while (l < r)
    {
        // 注意此处对(l + r) / 2做上取整,防止仅剩两个数时发生死循环
        // 注意:只在l = mid时才做如此处理
        int mid = (l + r + 1) / 2;
        if(a[mid] <= k) l = mid;
        else r = mid - 1;
    }
    return l;
}

int k, n;
int a[N];
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
while(~scanf("%d", &k))
{
    // 此处传入区间[0, n - 1]
    int pos = lowerBoundR(a, 0, n - 1, k);
    // 由于查找元素不存在时也返回0,故要对返回的数组下标做判断
    if(pos != 0 || (pos == 0 && a[0] <= k)) printf("%d\n", pos);
    else printf("-1\n");
}


// 4.在升序、有重复元素的数组中,查找第一个大于k值的元素的数组下标

const int N =  ;

// 在给定数组中查找第一个大于k值的元素的数组下标
// 若该元素不存在,则返回假设它存在,应该在的位置
// l为二分下界,r为二分上界
int upperBound(int a[], int l, int r, int k)
{
    while (l < r)
    {
        int mid = (l + r) / 2;
        // 相较于模板二,只是在此处去掉了等号
        if(a[mid] > k) r = mid;
        else l = mid + 1;
    }
    return l;
}

int k, n;
int a[N];
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
while(~scanf("%d", &k))
{
    // 此处传入区间[0, n]
    // 原因见模板二
    int pos = upperBound(a, 0, n, k);
    printf("%d\n", pos);
}

1.2.2 浮点数二分

注意:浮点数的精度要求

模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;// eps表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}


1.3 高精度

1.3.1 高精度加法

模板
// C = A + B, A >= 0, B >= 0

vector<int> add(vector<int> &A, vector<int> &B)// 用可变数组vector倒序存储大整数值,同时做引用可以避免对于整个vector的拷贝,提升效率
{
    if (A.size() < B.size()) return add(B, A);// 判断A和B的长度,保证A比B长

    vector<int> C;// 声明返回结果
    int t = 0;// 记录每一次的进位数
    for (int i = 0; i < A.size(); i ++ )
    {
        t += A[i];
        if (i < B.size()) t += B[i];// 判断当前B位上是否存在数字
        C.push_back(t % 10);// 保留
        t /= 10;// 进位
    }

    if (t) C.push_back(t);// 判断最后一位是否有进位
    return C;
}

string a, b;// 用string读入大整数
cin >> a >> b;

vector<int> A, B;// 倒序存入vector中,便于从低位开始计算
// 同时将字符转成数字,亦可写成A.push_back(a[i] - '0')
for(int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - 48);
for(int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - 48);

auto res = add(A, B);
for(int i = res.size(); i >= 0; i--) cout << res[i];// 正序输出

1.3.2 高精度减法

模板
// C = A - B, 满足A >= B, A >= 0, B >= 0

bool flag;

bool cmp(vector<int> &A, vector<int> &B)// 判断A,B大小,A大于等于B返回true,否则返回false
{
    if(A.size() != B.size()) return A.size() > B.size();
    for(int i = A.size() - 1; i >= 0; i--)
        if(A[i] != B[i]) return A[i] > B[i];
    return true;
}

vector<int> sub(vector<int> &A, vector<int> &B)
{
    vector<int> C;// 声明返回结果
    for (int i = 0, t = 0; i < A.size(); i ++ )// t记录每一次是否退位
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];// 判断当前B位是否存在
        C.push_back((t + 10) % 10);// 此时t有 ≥ 0 和 < 0 两种情况,将其合二为一,即先加10,再模10
        if (t < 0) t = 1;// 判断是否退位,退位则向上一位借1
        else t = 0;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();// 去掉多余的前导零
    return C;
}

string a, b;// 用string读入大整数
cin >> a >> b;

vector<int> A, B, C;// 倒序存入vector中,便于从低位开始计算
// 同时将字符转成数字,亦可写成A.push_back(a[i] - '0')
for(int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - 48);
for(int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - 48);

flag = cmp(A, B);// 给flag赋值
if(flag) C = sub(A, B);
else cout << '-', C = sub(B, A);
for(int i = C.size() - 1; i >= 0; i--) cout << C[i];// 从高位依次输出

1.3.3 高精度乘法(高精度乘低精度)——类比高精度加法

模板
// C = A * b, A >= 0, b >= 0

vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;// 声明返回结果

    int t = 0;// 记录每一次的进位数
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);// 存入个位数,即对10取模
        t /= 10;// 保留本次进位数
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();// 去掉多余的前导零

    return C;
}

string a;// 用string读入大整数
int b;
cin >> a >> b;

vector<int> A, C;// 倒序存入vector中,便于从低位开始计算
// 同时将字符转成数字,亦可写成A.push_back(a[i] - '0')
for(int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - 48);

C = mul(A, b);
for(int i = C.size() - 1; i >= 0; i--) cout << C[i];

1.3.4 高精度除法(高精度除以低精度)

模板
// A / b = C ... r, A >= 0, b > 0

vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;// 声明返回结果
    r = 0;// 记录每一次的余数
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];// 上一位的余数到下一位计算时翻了十倍
        C.push_back(r / b);// 存入整除b的结果
        r %= b;// 保留本次余数
    }
    reverse(C.begin(), C.end());// 翻转数组C,保证低位在前
    while (C.size() > 1 && C.back() == 0) C.pop_back();// 去掉多余的前导零
    return C;
}

string a;// 用string读入大整数
int b, r;// 被除数和余数
cin >> a >> b;

vector<int> A, C;// 倒序存入vector中,便于从低位开始计算
// 同时将字符转成数字,亦可写成A.push_back(a[i] - '0')
for(int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - 48);

C = div(A, b, r);
for(int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
cout << r << endl;


1.4 前缀和与差分

1.4.1 前缀和

一、一维前缀和

一维前缀和简单来说就是中学数学中的数列 {an}\{a_n\} 的前 nn 项和 SnS_n

原数组:a1,a2,,ana_1, a_2, \cdots, a_n

前缀和:Si=a1+a2++aiS_i = a_1 + a_2 + \cdots + a_i

边界:S0=0S_0 = 0

关系:ai=SiSi1a_i = S_i - S_{i - 1}

作用:快速求出原数组中某一段元素的和 al++ar=SrSl1a_l + \cdots + a_r = S_r - S_{l - 1}(时间复杂度为 O(1)O(1)

模板
const int N = 1e5 + 10;

int n, m;
int a[N], S[N];

scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);

for(int i = 1; i <= n; i++) S[i] = S[i - 1] + a[i];//前缀和的初始化

while(m--)
{
    int l, r;
    scanf("%d%d", &l,&r);
    printf("%d", S[r] - S[i - 1]);//前缀和的计算
}

二、二维前缀和

原矩阵:

[a(1,1)a(1,2)a(1,n)a(2,1)a(2,2)a(2,n)a(n,1)a(n,2)a(n,n)]\begin{bmatrix} a_{(1, 1)}&a_{(1, 2)}&\cdots&a_{(1, n)}\\ a_{(2, 1)}&a_{(2, 2)}&\cdots&a_{(2, n)}\\ \vdots&\vdots&\ddots&\vdots\\ a_{(n, 1)}&a_{(n, 2)}&\cdots&a_{(n, n)} \end{bmatrix}

前缀和矩阵:

[S(1,1)S(1,2)S(1,n)S(2,1)S(2,2)S(2,n)S(n,1)S(n,2)S(n,n)]\begin{bmatrix} S_{(1, 1)}&S_{(1, 2)}&\cdots&S_{(1, n)}\\ S_{(2, 1)}&S_{(2, 2)}&\cdots&S_{(2, n)}\\ \vdots&\vdots&\ddots&\vdots\\ S_{(n, 1)}&S_{(n, 2)}&\cdots&S_{(n, n)} \end{bmatrix}

关系:S(i,j)S_{(i, j)} = 原矩阵第 iijj 列格子左上部分所有元素的和

作用:快速求出原矩阵中以 (x1,y1)(x_1, y_1) 为左上角,(x2,y2)(x_2, y_2) 为右下角的子矩阵的和 =S(x2,y2)S(x11,y2)S(x2,y11)+S(x11,y11)= S_{(x_2, y_2)} - S_{(x_1 - 1, y_2)} - S_{(x_2, y_1 - 1)} + S_{(x_1 - 1, y_1 - 1)}

模板
const int N =  ;

int n, m, q;
int a[N][N], S[N][N];

scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        	scanf("%d", &a[i][j]);

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] + a[i][j];//初始化前缀和

while(q--)
{
    int xi, y1, x2, y2;
    scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    prinf("%d\n", S[x2][y2] - S[x1 - 1][y2] - S[x2][y1 - 1] + S[x1 - 1][y1 - 1]);//计算子矩阵和
}

1.4.2 差分——前缀和的逆运算

一、一维差分

再次类比中学数学中的数列,差分可以理解成对于原数列 {an}\{a_n\} 我们能否寻找到一个数列 {bn}\{b_n\} 使得 bi=aiai1 (i>1)b_i = a_i - a_{i - 1} \ (i > 1) 恒成立

原数组:a1,a2,,ana_1, a_2, \cdots, a_n

差分:bi=aiai1b_i = a_i - a_{i - 1}

关系:ai=b1+b2++bia_i = b_1 + b_2 + \cdots + b_i

作用:给原数组里区间 [l,r][l, r] 中的每个元素加上 cc,操作是B[l] += c, B[r + 1] -= c

模板
const int N =  ;

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}

int n, m;
int  a[N], b[N];

scanf("%d%d", &n, &m);
//初始化原数组
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);

//初始化差分数组,原理是把a[i]看作一个小区间,则l = r,故直接 insert(i, i, a[i]);
for(int i = 1; i <= n; i++) insert(i, i, a[i]);

while(m--)
{
    int l, r, c;
    scanf("%d%d%d", &l, &r, &c);
    intsert(l, r, c);
}

//求前缀和
for(int i = 1; i <= n; i++) b[i] += b[i - 1];

//输出
for(int i = 1; i <= n; i++) printf("%d ", b[i]);

二、二维差分

原矩阵:

[a(1,1)a(1,2)a(1,n)a(2,1)a(2,2)a(2,n)a(n,1)a(n,2)a(n,n)]\begin{bmatrix} a_{(1, 1)}&a_{(1, 2)}&\cdots&a_{(1, n)}\\ a_{(2, 1)}&a_{(2, 2)}&\cdots&a_{(2, n)}\\ \vdots&\vdots&\ddots&\vdots\\ a_{(n, 1)}&a_{(n, 2)}&\cdots&a_{(n, n)} \end{bmatrix}

差分矩阵:

[b(1,1)b(1,2)b(1,n)b(2,1)b(2,2)b(2,n)b(n,1)b(n,2)b(n,n)]\begin{bmatrix} b_{(1, 1)}&b_{(1, 2)}&\cdots&b_{(1, n)}\\ b_{(2, 1)}&b_{(2, 2)}&\cdots&b_{(2, n)}\\ \vdots&\vdots&\ddots&\vdots\\ b_{(n, 1)}&b_{(n, 2)}&\cdots&b_{(n, n)} \end{bmatrix}

关系:a(i,j)a_{(i, j)} = 差分矩阵中第 iijj 列格子左上部分所有元素的和

作用:给以 (x1,y1)(x_1, y_1) 为左上角,(x2,y2)(x_2, y_2) 为右下角的子矩阵中的所有元素加上 cc,操作是S[x1][y1] += c, S[x2 + 1][y1] -= c, S[x1][y2 + 1] -= c, S[x2 + 1][y2 + 1] += c

模板
const int N =  ;

int n, m, q;
int a[N][N], b[N][N];

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[x1 + 1][y2 + 1] += c;
}

scanf("%d%d%d", &n, &m, &q);

//初始化原矩阵
for(int i = 1; i <= n; i++)
	for(int j = 1; i <= m; i++)
        scanf("%d", &a[i][j]);

//初始化差分矩阵,原理是把a[i][j]看作是一个1×1的小矩阵,则x1 = x2,y1 = y2,故直接 insert(x1, y1, x2 ,y2, c);
for(int i = 1; i<= n; i++)
    for(int j = 1; i <= m; j++)
        insert(i, j, i, j, a[i][j]);

while(q--)
{
    int x1, y1, x2, y2, c;
    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++)
        b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];

//输出
for(int i = 1; i <= n; i++)
{
    for(int j = 1; j <= m; j++)
        printf("%d", b[i][j]);
	put("");
}


1.5 双指针算法——核心:优化

双指针算法的本质就两个指针同时在序列上单调移动,相对于暴力求解的内外循环嵌套。因此关键就在于从序列中寻找到合理的单调关系,进而进行优化

常见问题分类:

  1. 对于一个序列,用两个指针维护一段区间

  2. 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

作用:将朴素算法(暴力求解)的时间复杂度 O(n2)O(n^2) 降为 O(n)O(n)

模板
for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}

典例 AcWing 799. 最长连续不重复子序列

题解

const int N = 1e5 + 10;

int a[N], s[N];// a记录原数组,s记录原数组中在当前区间中每个元素的数量

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++)// 双指针维护区间:[j, i]
{
    s[a[i]]++;// 表示区间从右端点增加一个数
    while(s[a[i]] > 1)
    {
        s[a[j]]--;// 表示区间从左端点减去一个数
        j++;
    }
    res = max(res, i - j + 1);// 取二者较大值
}
cout << res << endl;

典例 AcWing 800. 数组元素的目标和

题解

const int N = 1e5 + 10;

int a[N], b[N];

int n, m, x;
cin >> n >> m >> x;
for(int i = 0; i < n; i++) cin >> a[i];
for(int i = 0; i < m; i++) cin >> b[i];

for(int i = 0, j = m - 1; i < n; i++)
{
    while(j >= 0 && a[i] + b[j] > x) j--;
    if(a[i] + b[j] == x)
    {
        cout << i << ' ' << j << endl;
        break;
    }
}


1.6 位运算

作用1:十进制数n的二进制表示中第k位的数字

原理:

① 先把第k位移到末位 n >> k

② 看个位数是几 n & 1

结合①和②得:n >> k & 1

作用2:lowbit函数,即返回二进制数的最后一位1

操作:x & (~x + 1)

原理:

           x = 10100001000
          ~x = 01011110111
      ~x + 1 = 01011111000
x & (~x + 1) = 00000001000


1.7 离散化(针对整数)——核心:映射

定义:离散化,就是当我们只关心数据的大小关系时,用排名代替原数据进行处理的一种预处理方法

本质:离散化本质上是一种哈希,它在保持原序列大小关系的前提下把其映射成正整数

特点:数据的值域很大,同时元素存在负数、小数时,难以作为数组下标,但内部元素的分布通常很稀疏(和哈希表的特点很类似)

模板
vector<int> alls;// 存储所有待离散化的值
sort(alls.begin(), alls.end());// 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());// 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x)// 找到第一个大于等于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;// 映射到1, 2, ...n
}

典例 AcWing 802. 区间和

题解

const int N = 300010;

typedef pair<int, int> PII;

int n, m;
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;
}

cin >> n >> m;
    
for(int i = 0; i < n; i++)
{
    int x, c;
    cin >> x >> c;
    add.push_back({x, c});
    
    alls.push_back(x);
}

for(int i = 0; i < m; i++)
{
    int 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 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), r = find(item.second);
    cout << s[r] - s[l - 1] << endl;
}

std::unique()的返回值是一个迭代器(对于数组来说就是指针了),它表示去重后容器中不重复序列的最后一个元素的下一个元素。所以可以这样作差求得不重复元素的数量。而对于 Python 或 JAVA 等没有unique函数,可以将其写成以下函数,然后进行调用

vector<int>::iterator unique(vector<int> &a)
{
    int j = 0;
    for(int i = 0; i < n; i++)
        //对于一个有序数组,如果是第一个元素(即a[0])或下一个元素和当前元素不同(a[i] ≠ a[i - 1]),则说明该元素首次出现
    	if(!i || a[i] != a[i - 1])
            a[j++] = a[i];
    //此时a[0] ~ a[j - 1]为原数组中所有不重复的元素
    return a.begin() + j;
}

离散化也不一定要从小到大排序,有时候也需要从大到小



1.8 区间合并

步骤:

(1) 按每个区间左端点值从小到大排序

(2) 设当前区间的左端点为 stst ,右端点为 eded ,下一个区间的左端点 ll ,右端点 rr ,则会出现三种情况:

stl<redst \leqslant l < r \leqslant ed 区间更新:不变

stled<rst \leqslant l \leqslant ed < r 区间更新:右端点 eded 换成 rr ,即取二者的并集

st<ed<l<rst < ed < l < r 区间更新:区间 ststeded 整体更新为 llrr

模板
const int INF =  ;
typedef pair<int, int> PII;

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());// 对所有pair排序(左端点y

    int st = -INF, ed = -INF;
    for(auto item : segs)
        if(ed < item.first)// ③ st < ed < l < r 区间更新:区间st到ed整体更新为l到r
        {
            // 需要先判断保证维护区间不是初始区间
            if(st != -INF) res.push_back({st, ed});
            st = item.first, ed item.second;
        }
        // ① st <= l < r <= ed区间更新:不变
    	// ② st <= l <= ed < r区间更新:右端点ed换成r,即取二者的并集
    	else ed = max(ed, item.second);

    if(st != -INF) res.push_back({st, ed});// 将最后的区间加入最终答案

    segs = res;// segs更新为res
}


参考资料:

[1] yxc. AcWing算法基础课常用代码模板1——基础算法. 原文链接

[2] MokylinJay. 整数二分详解. 原文链接


部分图片素材来自网络,如有侵权请联系删除