本周首先把上周剩下的莫队与其他算法糅杂在一起的题型的相关博客看了,然后看了主席树的相关博客,然后就是看相关的习题,后续就是大面积的分治,还有线段树,其实线段树是主席树的前置知识,在学习主席树的时候也看了看线段树的相关,后续学线段的时候可以更好上手一些吧。
对于主席树这个算法和莫队一样都属于离线算法,它的主体是线段树,准确的说,是很多棵线段树,因而要学会前置算法线段树。对于线段树的每个结点,保存的是这个区间含有的数字的个数。然后主席树的每个结点,也就是每颗线段树的大小和形态也是一样的,每个节点表示在范围内的树的个数,这也就是主席树之间可以相互进行加减运算。而进行加减运算就表示出了第n棵到第m棵树之前的区间内元素的个数,产生一个新的区间对应的线段树,这样一来,任意一个区间的线段树,都可以由我这n个基础区间表示出来了。然后再进行查询就好说了。
但是显然的是如果建立n个相同的树空间太大,并且如果在两个相邻的树中,如果节点的权值不变,就造成了有很多重复的元素,所以可以优化整个树,对与第n+1树,若n+1与n的对应节点权值不变就连到n上,如果变了,就新建一个节点,这样可以大大减少冗余的节点。
主席树主要的思想和优化差不多就是这样,然后主要是代码实现,
贴个模版题吧,这里主要是注意如何在建立树的时候进行代码的优化。
#include <bits/stdc++.h>
#define maxn 200010
#define mmax 1000070
using namespace std;
int a[maxn], b[maxn], n, m, q, p, sz;
int lc[mmax], rc[mmax], sum[mmax], rt[mmax];
inline int read()//经典快读
{
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
void build(int &rt, int l, int r)//建立函数
{
sz++;
rt = sz, sum[rt] = 0;
if (l == r)
return;
int mid = (l + r) / 2;
build(lc[rt], l, mid);
build(rc[rt], mid + 1, r);
}
int update(int o, int l, int r)
{
sz++;
int oo =sz;
lc[oo] = lc[o], rc[oo] = rc[o], sum[oo] = sum[o] + 1;
if (l == r) return oo;
int mid = (l + r) /2;
if (mid >= p)
lc[oo] = update(lc[oo], l, mid);
else
rc[oo] = update(rc[oo], mid + 1, r);
return oo;
}
int query(int u, int v, int l, int r, int k)
{
int mid = (l + r) >> 1, x = sum[lc[v]] - sum[lc[u]];
if (l == r)
return l;
if (x >= k)
return query(lc[u], lc[v], l, mid, k);
else return query(rc[u], rc[v], mid + 1, r, k - x);
}
int main(){
n = read(), m = read();
for (int i = 1; i <= n; ++i)
{a[i] = read();
b[i] = a[i];}
sort(b + 1, b + 1 + n);
q = unique(b + 1, b + 1 + n) - b - 1;
build(rt[0], 1, q);
for (int i = 1; i <= n; i++)//这里建树
{
p = lower_bound(b + 1, b + 1 + q, a[i]) - b;
rt[i] = update(rt[i - 1], 1, q);
}
while (m--)//进行查询
{
int l = read(), r = read(), k = read();
cout<<b[query(rt[l - 1], rt[r], 1, q, k)]<<endl;
}
return 0;
}
这道题的解答区有位大佬的博客详细的讲了主席树的开树过程(编的名字还挺上口),并且也之路了前置知识的教程。
这里只是离线的主席树,在老师给的博客里还提到了动态的主席树,动态主席树说到底是线段树套线段树(外层可以简化为树状数组),而静态主席树是重复利用的线段树, (又提到树状数组了,这个知识真的很有用)。
另外在看相关题型的时候又看到熟悉的 HH的项链,这不是莫队那边的模版题的数据加强版吗。。用主席树做的话代码会简练一点,但是又莫队模版的话莫队真的很暴力且好用。
下周的话直接先看线段树吧,学一学线段树再回来搞一搞更复杂的主席树。