省赛主席树模板题和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;
}//离散化