主席树是函数式线段树的前缀和或树状数组套函数式线段树。一般来说的主席树是树状数组套函数式线段树……——VFleaKing
其实关于这个东西Seter已经讲的很清楚了。我就讲讲具体实现方法吧(非递归)。
-
函数式线段树的前缀和
先来看一道例题:poj2104
建树
我们其实是要建n棵线段树,我们用root[i]代表第I棵线段树的根。从1到n建过去。
void update(int old,int l,int r,const int num,int &root)
{
root = Vc + 1;
for (int mid;l != r;) {
mid = (l + r) / 2;
tree[++Vc] = tree[old]; ++tree[Vc].sum;
if (num > mid) old = tree[old].rc,tree[Vc].rc = Vc + 1,l = mid + 1;
else old = tree[old].lc,tree[Vc].lc = Vc + 1,r = mid;
}
tree[++Vc] = node(0,0,tree[old].sum + 1);
}
这个Update操作就是建第i棵线段树的操作,old为i-1棵线段树的根, l为线段树的左边界, r为右边界, num为要插入的数, fa为一个指针,为这棵线段树当前点的父节点,这样方便更新结点的儿子信息。 ,root为根。具体见代码。
可以这样调用(没有离散化等优化的情况下):
root[0] = ++Vc;
for (int i = 1; i <= n; ++i)
update(root[i - 1],1,max_ai,a[i],root[i]);
询问
询问其实跟普通线段树一样,用root[r]的值减掉root[l - 1]的值就可以得到a[l]...a[r]建出来的线段树的值了
int query(int l,int r,int rank)
{
int lb = 1,ub = tmp[0],t;
for (; lb != ub; ) {
int mid = (lb + ub) / 2;
if ((t = tree[tree[r].lc].sum - tree[tree[l].lc].sum) < rank) {
rank -= t,lb = mid + 1;
l = tree[l].rc;
r = tree[r].rc;
}else {
ub = mid;
l = tree[l].lc;
r = tree[r].lc;
}
}
return tmp[lb];
}
离散化
我直接用stl的函数了
std::sort(tmp + 1,tmp + 1 + n);
tmp[0] = std::unique(tmp + 1,tmp + 1 + n) - tmp - 1;
用的时候像这样
std::lower_bound(tmp + 1,tmp + 1 + tmp[0],seq[boundary]) - tmp
时间复杂度是O(N*logN),空间复杂度O(N*logN)
代码:
#include <cstdio> #include <algorithm> const int N = 100000 + 9; struct node { int lc,rc,sum; node(const int a = 0,const int b = 0,const int c = 0): lc(a),rc(b),sum(c){} }tree[N*18]; int tmp[N],n,m,root[N],Vc,a[N],seq[N]; void update(int old,int l,int r,const int num,int &root) { root = Vc + 1; for (int mid;l != r;) { mid = (l + r) / 2; tree[++Vc] = tree[old]; ++tree[Vc].sum; if (num > mid) old = tree[old].rc,tree[Vc].rc = Vc + 1,l = mid + 1; else old = tree[old].lc,tree[Vc].lc = Vc + 1,r = mid; } tree[++Vc] = node(0,0,tree[old].sum + 1); } int query(int l,int r,int rank) { int lb = 1,ub = tmp[0],t; for (; lb != ub; ) { int mid = (lb + ub) / 2; if ((t = tree[tree[r].lc].sum - tree[tree[l].lc].sum) < rank) { rank -= t,lb = mid + 1; l = tree[l].rc; r = tree[r].rc; }else { ub = mid; l = tree[l].lc; r = tree[r].lc; } } return tmp[lb]; } int main() { #ifndef ONLINE_JUDGE freopen("2104.in","r",stdin); freopen("2104.out","w",stdout); #endif scanf("%d%d",&n,&m); for (int i = 1; i <= n; ++i) { scanf("%d",seq + i); tmp[i] = seq[i]; } std::sort(tmp + 1,tmp + 1 + n); tmp[0] = std::unique(tmp + 1,tmp + 1 + n) - tmp - 1; root[0] = ++Vc; /*tree[Vc].lb = 1; tree[Vc].rb = tmp[0]*/; for (int x,y,z,boundary = 1; m--;) { scanf("%d%d%d",&x,&y,&z); if (boundary <= y) for (; boundary <= y; ++boundary) update(root[boundary - 1],1,n,a[boundary] ? a[boundary] : a[boundary] = std::lower_bound(tmp + 1,tmp + 1 + tmp[0],seq[boundary]) - tmp,root[boundary]); printf("%d ",query(root[x - 1],root[y],z)); } }
还有一道题,也是借鉴了函数式编程的思想:bzoj3261: 最大异或和
也是运用了函数式编程的思想。这个我将另写题解。