主席树学习笔记

概述

这篇文章是我对学习主席树的一个小结。

当然,笔者很菜,除了模板就什么都不会打,也会有一些知识理解得不够深刻。

因此,这篇文章以后还会不定期更新的。

主席树

主席树,全名称为可持久化线段树,也就是把每个阶段的线段树保存起来,每次可以直接访问一个阶段的状态,是神犇(HJT)发明出来的一种数据结构。

先放出一个经典模板题:静态区间第(k)

题目描述

如题,给定(N)个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入输出格式

输入格式

第一行包含两个正整数(N)(M),分别表示序列的长度和查询的个数。

第二行包含(N)个整数,表示这个序列各项的数字。

接下来(M)行每行包含三个整数(l, r, k) , 表示查询区间([l, r])内的第(k)小值。

输出格式

输出包含(k)行,每行(1)个整数,依次表示每一次查询的结果

输入输出样例

输入样例#1

5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1

输出样例#1

6405
15770
26287
25957
26287

说明

数据范围

对于(20\%)的数据满足:(1 leq N, M leq 10)

对于(50\%)的数据满足:(1 leq N, M leq 10^3)

对于(80\%)的数据满足:(1 leq N, M leq 10^5)

对于(100\%)的数据满足:(1 leq N, M leq 2cdot 10^5)

对于数列中的所有数(a_i)​,均满足(-{10}^9 leq a_i leq {10}^9)

样例数据说明

(N=5),数列长度为(5),数列从第一项开始依次为([25957, 6405, 15770, 26287, 26465 ])

第一次查询为([2, 2])区间内的第一小值,即为(6405)

第二次查询为([3, 4])区间内的第一小值,即为(15770)

第三次查询为([4, 5])区间内的第一小值,即为(26287)

第四次查询为([1, 2])区间内的第二小值,即为(25957)

第五次查询为([4, 4])区间内的第一小值,即为(26287)

题解

经典的主席树题。

由于本题中的(a_i)数据范围很大,因此我们需要将数据进行离散化处理。

然后就开始执行主席树的操作。

先来看看插入操作的代码:

void add(int &num, int &x, int l, int r)
{
    T[cnt++] = T[x], x = cnt - 1, ++T[x].sum;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (num <= mid) add(num, T[x].L, l, mid);
    else add(num, T[x].R, mid + 1, r);
}

和线段树很相似,不是吗?(主席树不就是可持久化的线段树吗)

接着看寻找答案的代码:

int getans(int i, int j, int k, int l, int r)
{
    if (l == r) return l;
    int t = T[T[j].L].sum - T[T[i].L].sum, mid = (r + l) >> 1;
    if (k <= t) return getans(T[i].L, T[j].L, k, l, mid);
    else return getans(T[i].R, T[j].R, k - t, mid + 1, r);
}

和线段树的思想一样,分开去找左右两子树。

代码就这么一点儿,并不难记。

不难得出最后的代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cctype>
#include <vector>
#include <set>
#include <map>
#include <string>

using namespace std;

inline int gi()
{
	int f = 1, x = 0; char c = getchar();
	while (c < '0' || c > '9') { if (c == '-')f = -1; c = getchar();}
	while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar();}
	return f * x;
}

struct Node
{
    int L, R, sum;
} T[4000003];
struct lisan
{
    int x, idx;
    bool operator < (const lisan &rhs) const
	{
        return x < rhs.x;
    }
} a[4000003];
int r[2000003], t[2000003], n, m, cnt;

void add(int &num, int &x, int l, int r)
{
    T[cnt++] = T[x], x = cnt - 1, ++T[x].sum;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (num <= mid) add(num, T[x].L, l, mid);
    else add(num, T[x].R, mid + 1, r);
}

int getans(int i, int j, int k, int l, int r)
{
    if (l == r) return l;
    int t = T[T[j].L].sum - T[T[i].L].sum, mid = (r + l) >> 1;
    if (k <= t) return getans(T[i].L, T[j].L, k, l, mid);
    else return getans(T[i].R, T[j].R, k - t, mid + 1, r);
}

int main()
{
    n = gi(), m = gi();
	for (int i = 1; i <= n; i++)
    {
        a[i].x = gi(), a[i].idx = i;
    }
    sort(a + 1, a + n + 1);//离散化
    for (int i = 1; i <= n; i++) r[a[i].idx] = i;
    cnt = 1;
    for (int i = 1; i <= n; i++)
	{
        t[i] = t[i - 1], add(r[i], t[i], 1, n);//建树
    }
    while (m--)
	{
        int x = gi(), y = gi(), z = gi();
        printf("%d
", a[getans(t[x - 1], t[y], z, 1, n)].x);//输出答案
    }
	return 0;
}

可持久化数组

可持久化数组属于主席树的一种变形,它是带修改的。

即:可持久化数组是一种支持单点修改、单点查询的可持久化数据结构。

我怎么感觉可持久化数组比主席树还简单啊

还是来边看代码边理解思路:

定义一个结构体,存储每个节点的信息:左儿子、右儿子、权值。

struct Node
{
	int l, r, id;
} q[20000005];

新建一个节点:

inline int newnode(int x)
{
	q[++tot] = q[x];
	return tot;
}

来看建树的代码(其实就是新建节点的过程):

int jianshu(int node, int beg, int en)
{
	node = ++tot;
	if (beg == en)
	{
		q[node].id = a[beg];
		return tot;
	}
	int mid = (beg + en) >> 1;
	q[node].l = jianshu(q[node].l, beg, mid);//左儿子
	q[node].r = jianshu(q[node].r, mid + 1, en);//右儿子
	return node;
}

更新数值:

int modify(int node, int beg, int en, int x, int y)
{
	node = newnode(node);//新建一个节点
	if (beg == en)
		q[node].id = y;//更新完了
	else
	{
		int mid = (beg + en) >> 1;
		if (x <= mid)
			q[node].l = modify(q[node].l, beg, mid, x, y);//更新左子树
		else
			q[node].r = modify(q[node].r, mid + 1, en, x, y);//更新右子树
	}
	return node;
}

寻找答案:

int getans(int node, int beg, int en, int x)
{
	if (beg == en)
		return q[node].id;//已经找到答案了
	else
	{
		int mid = (beg + en) >> 1;
		if (x <= mid)
			return getans(q[node].l, beg, mid, x);//向左寻找
		else
			return getans(q[node].r, mid + 1, en, x);//向右寻找
	}
}

不难得出全部代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>

using namespace std;

inline int gi()
{
	int f = 1, x = 0;
	char c = getchar();
	while (c < '0' || c > '9')
	{
		if (c == '-')
			f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = x * 10 + c - '0';
		c = getchar();
	}
	return f * x;
}

int n, m, ans, a[1000005], tot, root[1000005];
struct Node
{
	int l, r, id;
} q[20000005];

inline int newnode(int x)
{
	q[++tot] = q[x];
	return tot;
}

int jianshu(int node, int beg, int en)
{
	node = ++tot;
	if (beg == en)
	{
		q[node].id = a[beg];
		return tot;
	}
	int mid = (beg + en) >> 1;
	q[node].l = jianshu(q[node].l, beg, mid);
	q[node].r = jianshu(q[node].r, mid + 1, en);
	return node;
}

int modify(int node, int beg, int en, int x, int y)
{
	node = newnode(node);
	if (beg == en)
		q[node].id = y;
	else
	{
		int mid = (beg + en) >> 1;
		if (x <= mid)
			q[node].l = modify(q[node].l, beg, mid, x, y);
		else
			q[node].r = modify(q[node].r, mid + 1, en, x, y);
	}
	return node;
}

int getans(int node, int beg, int en, int x)
{
	if (beg == en)
		return q[node].id;
	else
	{
		int mid = (beg + en) >> 1;
		if (x <= mid)
			return getans(q[node].l, beg, mid, x);
		else
			return getans(q[node].r, mid + 1, en, x);
	}
}

int main()
{
	n = gi(), m = gi();
	for (int i = 1; i <= n; i++)
		a[i] = gi();
	root[0] = jianshu(0, 1, n);//建树
	for (int i = 1; i <= m; i++)
	{
		int rf = gi(), fl = gi(), x = gi();
		if (fl == 1)
		{
			int y = gi();
			root[i] = modify(root[rf], 1, n, x, y);//保存状态+修改
		}
		else
		{
			printf("%d
", getans(root[rf], 1, n, x));//输出答案
			root[i] = root[rf];//保存状态
		}
	}
	return 0;
}

总结

主席树和可持久化数组的代码很短,但是,理解思路才是最重要的,它们的思想也很重要。

The End.

原文地址:https://www.cnblogs.com/xsl19/p/11099885.html