主席树

省赛主席树模板题和zyn大佬想了两个小时没想出来 太菜了太菜了

要是我能强到和HJT大神一样能在考场上想出一个主席树一样的东西就好了哈哈哈

 

感觉主席树就是一个线段树加前缀和加一个优化

主要用于求区间第k小的问题

如果区间是固定的 用线段树或者是归并都好求

用线段树的话 每个节点就存这个区间有的数的个数 如果要查询的k比节点左子树的权值要小于等于的话

说明这个区间的左孩子里至少有k个数 那第k小肯定就在左孩子区间里了

 

如果要求一个动态的区间【L,R】中的第k小

我们可以对区间【1,L】和【1,R】建线段树

因为线段树的节点可以相加减

所以结果就类似于前缀和 减一下 就可以了

但是这样的话时间和空间都会爆

 

主席树就是发现 每次更改一个节点的值的时候 一棵树上只有一条路径会被更改 也就是logn个节点

第i棵线段树和第i+1棵 只需要修改logn个节点

所以每次只创建和上次不同的节点 相同的就用之前的就可以了

 

为了节省空间,可以将第 00 棵线段树置为空,每次插入一个新叶子节点时接入一条长度为 O(\log n)O(logn)的链。总空间、时间复杂度仍为 O(n \log n)O(nlogn)

查询时构造整棵线段树,需要构造 O(n \log n)O(nlogn) 个节点,但每次查询只会用到 O(\log n)O(logn) 个节点,直接动态构造这些节点即可。为了方便,可以不显式构造这些节点,而是直接用两棵线段树上的值相减。

 

需要注意的是

主席树需要动态开点

因为普通的线段树 我们可以算出左右孩子的下标

但是主席树是变化的 就需要动态开点 每个节点要多存一下左右孩子的下标

 

来!上模板!

struct node{
    int sum, l, r;//l到r之间数的个数 左儿子右儿子编号
}T[maxn * 40];
int x, y, k;
int root[maxn], cnt, a[maxn], n, m;

void update(int l, int r, int &x, int y, int pos)
{
    T[++cnt] = T[y];
    T[cnt].sum++;
    x = cnt;
    if(l == r) return;
    int mid = (l + r) / 2;
    if(mid >= pos)
        update(l, mid, T[x].l, T[y].l, pos);
    else
        update(mid + 1, r, T[x].r, T[y].r, pos);
}

int query(int l, int r, int x, int y, int k)
{
    if(l == r) return l;
    int mid = (l + r) / 2;
    int sum = T[T[y].l].sum - T[T[x].l].sum;
    if(sum >= k) return query(l, mid, T[x].l, T[y].l, k);
    return query(mid +1, r, T[x].r, T[y].r, k - sum);
}

vector<int>v;
int getid(int x)
{
    return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}//离散化


posted @ 2018-05-14 21:26  wyboooo  阅读(79)  评论(0编辑  收藏  举报