Luckyleaves's Blog

stay hungry,stay foolish greedy and lucky

可持久化线段树(主席树)

可持久化前提

  • 数据结构本身的拓扑结构不变

局限性

  • 无法使用懒标记,很难进行区间修改(但可以标记永久化(局限性也大的不行))

所解决的问题

  • 查询数据结构的所有历史版本

核心思想

  • 只记录当前版本和之前版本不一样的地方-目的:降低算法空间复杂度,对于线段树的每一次修改,我们最多涉及$\log(n)$个节点,最多进行n次操作,空间复杂度级别就被降为$n\log(n)$。

  • 线段树建立

    1
    2
    3
    4
    struct Node{
    int l, r;//表示左右节点的下标
    int cnt;//当前区间中一共有多少个数
    };

例题:Acwing 查找第K小数

  • 时空复杂度:$n\log(n)$.

  • 我们不难发现(读题可知)只有100000个数,但每个数的大小可以到达1000000000,所以对于每一个数进行离散化在数值上建立线段树,维护每个数值区间中一共有多少个数

  • 我们先来考虑在$[1,n]$上维护k小值无标题

  • 我们还是不难发现,对于这个序列整体进行二分操作使左边的数的个数等于k-1即可,以此我们只需推论到区间$[l, r]$即可,我们再根据线段树的性质可知,每一个点所在的线段树的区间是一定的,且每一个数在线段树中出现的次数也是一定的,于是我们运用前缀和的思想,查询线段树上$[l, r]$的第k个值(因为它前面的数字都比它小) (听不懂的话,请看具体操作(语文还是太差了))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
const int N = 2e5 + 5, M = 100000;
int n, m;
int a[N];
struct Node{
int l, r;
int cnt;
}tr[(int)5e6];
int root[N], idx; // 每个版本的根节点
vector<int>nums;//离散化
int find(int x)
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
int build(int l, int r)
{
int p = ++ idx;
if(l == r) return p;
int mid = l + r >> 1;
tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
return p;
}
int insert(int p, int l, int r, int x)
{
int q = ++ idx;
tr[q] = tr[p];
if (l == r)
{
tr[q].cnt ++ ;
return q;
}
int mid = l + r >> 1;
if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
else tr[q].r = insert(tr[p].r, mid + 1, r, x);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
int query(int q, int p, int l, int r, int k)
{
if (l == r) return r;
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;//前缀和思想
int mid = l + r >> 1;
if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);//减去左区间的
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &a[i]);
nums.push_back(a[i]);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
root[0] = build(0, nums.size() - 1);
for (int i = 1; i <= n; i ++ )
root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
// for(int i = 0;i <= n;i ++) cout << root[i] << endl;
while(m --)
{
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);
}
return 0;
}
/*
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
*/

顺便捞一下(思想相同)luogu P3919可持久化线段树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
const int N = 2e6 + 5;
struct Node{
int l, r;
int v;
}tr[N << 4];
int n, m;
int a[N];
int idx;
int root[N];
int build(int l, int r)
{
int p = ++ idx;
if(l == r)
{
tr[p].v = a[l];
return p;
}
int mid = l + r >> 1;
tr[p].l = build(l, mid);
tr[p].r = build(mid + 1, r);
return p;
}
int modify(int q, int l, int r, int x, int y)
{
int p = ++ idx;
tr[p] = tr[q];
if(l == r)
{
tr[p].v = y;
return p;
}
int mid = l + r >> 1;
if(x <= mid) tr[p].l = modify(tr[q].l, l, mid, x, y);
else tr[p].r = modify(tr[q].r, mid + 1, r, x, y);
return p;
}
int query(int q, int l, int r, int x)
{
if(l == r) return tr[q].v;
int mid = l + r >> 1;
if(x <= mid) return query(tr[q].l, l, mid, x);
else return query(tr[q].r, mid + 1, r, x);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1;i <= n;i ++) scanf("%d", &a[i]);
root[0] = build(1, n);
int x, op, t, y;
for(int i = 1;i <= m;i ++)
{
scanf("%d%d%d", &x, &op, &t);
if(op == 1)
{
scanf("%d", &y);
root[i] = modify(root[x], 1, n, t, y);
}
if(op == 2)
{
printf("%d\n", query(root[x], 1, n, t));
root[i] = root[x];
}
}
return 0;
}
/*
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
*/