高级数据结构整理


原理就不讲了:小破站一搜一堆的,这里就放一些题目用于练习吧!参考连接也会附在相应位置!!!!

1、单调栈和单调队列

AcWing 154. 滑动窗口:单调队列
给定一个大小为 n ≤ 1 0 6 n≤10^6 n106 的数组。

有一个大小为 k k k 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k k k 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7] k k k 3 3 3

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式
输入包含两行。

第一行包含两个整数 n n n k k k,分别代表数组长度和滑动窗口的长度。

第二行有 n n n 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式
输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:

8 3
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int q[N],a[N];
int n,k;
int main(){
    scanf("%d%d", &n, &k);
    for(int i = 0 ;i <n ;i ++) scanf("%d", &a[i]);
    int tt = -1,hh = 0;
    for(int i =0 ;i < n; i++){
        while(tt >=hh && a[q[tt]]>a[i]){
            --tt;
        }
        q[++tt] = i;
        if(i+1>=k) printf("%d ",a[q[hh]]);
        if(i+1-q[hh]>=k) ++hh;
    }
    printf("\n");
    tt = -1,hh = 0;
    memset(q, 0, sizeof q);
    for(int i = 0 ;i <n ;i ++){
        while(tt>=hh && a[q[tt]]<a[i]){
            --tt;
        }
        q[++tt] = i;
        if(i+1>=k) printf("%d ",a[q[hh]]);
        if(i+1-q[hh]>=k) ++hh;
    }
    return 0;
}

Acwing830. 单调栈
给定一个长度为 N N N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 − 1 −1 1

输入格式
第一行包含整数 N N N,表示数列长度。

第二行包含 N N N 个整数,表示整数数列。

输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。

数据范围
1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^5 1N105
1 ≤ 数 列 中 元 素 ≤ 1 0 9 1\leq数列中元素\leq10^9 1109
输入样例:

5
3 4 2 7 5

输出样例

-1 3 -1 2 2
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N];
int q[N];
int main(){
    scanf("%d",&n);
    int tt = 0;
    for(int i = 1 ;i <= n ;i ++ ){ scanf("%d",&a[i]);}
    for(int i = 1 ;i <= n; i ++ ){
        while( tt &&  a[i] <= q[tt]){
            q[--tt];
        }
        printf("%d ",tt == 0 ? -1 : q[tt]);
        q[++tt] = a[i] ;
    }
    return 0;
}

2、并查集

3、树状数组:

  1. 用于单点修改,区间查询
  2. 用于区间修改,单点查询
  3. 用于区间修改,区间查询

用于单点修改,区间查询

Acwing241. 楼兰图腾
在完成了分配任务之后,西部 314 来到了楼兰古城的西部。

相传很久以前这片土地上(比楼兰古城还早)生活着两个部落,一个部落崇拜尖刀(V),一个部落崇拜铁锹(∧),他们分别用 V 和 ∧ 的形状来代表各自部落的图腾。

西部 314 314 314 在楼兰古城的下面发现了一幅巨大的壁画,壁画上被标记出了 n n n 个点,经测量发现这 n n n 个点的水平位置和竖直位置是两两不同的。

西部 314 314 314 认为这幅壁画所包含的信息与这 n 个点的相对位置有关,因此不妨设坐标分别为 ( 1 , y 1 ) , ( 2 , y 2 ) , … , ( n , y n ) (1,y_{1}),(2,y_{2}),…,(n,y_{n}) (1,y1),(2,y2),,(n,yn),其中 y 1 ∼ y n y_{1}∼y_{n} y1yn 1 1 1 n n n 的一个排列。

西部 314 314 314 打算研究这幅壁画中包含着多少个图腾。

如果三个点 ( i , y i ) , ( j , y j ) , ( k , y k ) (i,y_i),(j,y_j),(k,y_k) (i,yi),(j,yj),(k,yk) 满足 1 ≤ i < j < k ≤ n 1\leq i<j<k\leq n 1i<j<kn y i > y j , y j < y k y_i>y_j,y_j<y_k yi>yj,yj<yk,则称这三个点构成 V 图腾;

如果三个点 ( i , y i ) , ( j , y j ) , ( k , y k ) (i,y_i),(j,y_j),(k,y_k) (i,yi),(j,yj),(k,yk) 满足 1 ≤ i < j < k ≤ n 1\leq i<j<k\leq n 1i<j<kn y i < y j , y j > y k y_i<y_j,y_j>y_k yi<yj,yj>yk,则称这三个点构成 ∧ 图腾;

西部 314 想知道,这 n 个点中两个部落图腾的数目。

因此,你需要编写一个程序来求出 V 的个数和 ∧ 的个数。

输入格式
第一行一个数 n n n

第二行是 n n n 个数,分别代表 y 1 , y 2 , … , y n y_{1},y_{2},…,y_{n} y1y2,,yn

输出格式
两个数,中间用空格隔开,依次为 V 的个数和 ∧ 的个数。

数据范围
对于所有数据, n ≤ 200000 n\leq200000 n200000,且输出答案不会超过 int 64 64 64
y 1 ∼ y n y_{1}∼y_{n} y1yn 1 1 1 n n n的一个排列。

输入样例:

5
1 5 3 2 4

输出样例:

3 4
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n;
int great[N],lower[N];
int q[N],tr[N];
typedef long long LL;
int lowbit(int x)
{
    return x & -x;
}

void add(int x, int c)  // 位置x加c
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int query(int x)  // 返回前x个数的和
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main(){
    scanf("%d", &n);
    for(int i = 1 ;i <= n ;i ++) scanf("%d", &q[i]);
    for(int i = 1 ;i <= n ;i ++){
        int y = q[i];
        lower[i] = query(y-1);
        great[i] = query(n) - query(y);
        add(y,1);
    }
    memset(tr, 0, sizeof tr);
    LL res1 =0, res2 = 0;
    for(int i = n ; i; i--){
        int y = q[i];
        res1+= great[i] * (LL)(query(n)-query(y));
        res2 += lower[i] * (LL)(query(y-1));
        add(y,1);
    }
    printf("%lld %lld",res1,res2);
    return 0;
}

Acwing244. 谜一样的牛
参考题解
有 n 头奶牛,已知它们的身高为 1∼n 且各不相同,但不知道每头奶牛的具体身高。

现在这 n n n 头奶牛站成一列,已知第 i i i头牛前面有 A i A_{i} Ai 头牛比它低,求每头奶牛的身高。

输入格式
1 1 1 行:输入整数 n n n

2.. n 2..n 2..n 行:每行输入一个整数 A i A_{i} Ai,第 i i i 行表示第 i i i 头牛前面有 A i A_{i} Ai 头牛比它低。
(注意:因为第 1 1 1 头牛前面没有牛,所以并没有将它列出)

输出格式
输出包含 n n n 行,每行输出一个整数表示牛的身高。

i i i 行输出第 i i i 头牛的身高。

数据范围
1 ≤ n ≤ 1 0 5 1\leq n\leq10^{5} 1n105
输入样例:

5
1
2
1
0

输出样例:

2
4
5
3
1
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int h[N];
int ans[N];
int tr[N];

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int c)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int sum(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d", &n);
    for (int i = 2; i <= n; i ++ ) scanf("%d", &h[i]);

    for (int i = 1; i <= n; i ++ ) tr[i] = lowbit(i);
    for (int i = n; i; i -- )
    {
        int k = h[i] + 1;
        int l = 1, r = n;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (sum(mid) >= k) r = mid;
            else l = mid + 1;
        }
        ans[i] = r;
        add(r, -1);
    }

    for (int i = 1; i <= n; i ++ ) printf("%d\n", ans[i]);

    return 0;
}

AcWing 243. 一个简单的整数问题2
给定一个长度为 N N N 的数列 A A A,以及 M M M条指令,每条指令可能是以下两种之一:

  1. C l r d,表示把 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],,A[r] 都加上 d d d
  2. Q l r,表示询问数列中第 l ∼ r l∼r lr 个数的和。

对于每个询问,输出一个整数表示答案。

输入格式
第一行两个整数 N , M N,M N,M

第二行 N N N个整数 A [ i ] A[i] A[i]

接下来 M M M行表示 M M M条指令,每条指令的格式如题目描述所示。

输出格式
对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围:

1 ≤ N , M ≤ 105 1 \leq N,M\leq105 1N,M105,
∣ d ∣ ≤ 10000 |d|\leq 10000 d10000,
∣ A [ i ] ∣ ≤ 109 |A[i]|\leq 109 A[i]109

输入样例:

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

输出样例:

4
55
9
15
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m;
int a[N];
LL tr1[N];  // 维护b[i]的前缀和
LL tr2[N];  // 维护b[i] * i的前缀和

int lowbit(int x)
{
    return x & -x;
}

void add(LL tr[], int x, LL c)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

LL sum(LL tr[], int x)
{
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

LL prefix_sum(int x)
{
    return sum(tr1, x) * (x + 1) - sum(tr2, x);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ )
    {
        int b = a[i] - a[i - 1];
        add(tr1, i, b);
        add(tr2, i, (LL)b * i);
    }

    while (m -- )
    {
        char op[2];
        int l, r, d;
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q')
        {
            printf("%lld\n", prefix_sum(r) - prefix_sum(l - 1));
        }
        else
        {
            scanf("%d", &d);
            // a[l] += d
            add(tr1, l, d), add(tr2, l, l * d);
            // a[r + 1] -= d
            add(tr1, r + 1, -d), add(tr2, r + 1, (r + 1) * -d);
        }
    }

    return 0;
}

参考题解

4、线段树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值