About:About:About:主席树的由来
一位名字缩写为hjthjthjt的DalaoDalaoDalao前国家领导人在考场上的yyyyyy
有请经典例题的Describe:Describe:Describe:
给定N个整数构成的序列,对于指定的闭区间查询其区间内的第K小值。
好像和可持久化毫无关系QAQQAQQAQ
我会权值线段树!
我们发现它没法维护原始区间信息。
那就让它能维护!
建立NNN棵值域相同的线段树,其中SegiSeg_{i}Segi储存了A[1,i]A_{[1,i]}A[1,i]的信息。
值域相同意味着它们大小相同
于是它们的信息可以互相加减。(很重要的性质!
对于区间[l,r][l,r][l,r],SegrSeg_{r}Segr的[a,b][a,b][a,b](值域) 节点减去Segl−1Seg_{l - 1}Segl−1的[a,b][a,b][a,b]节点就可以得到[a,b][a,b][a,b]在[l,r][l,r][l,r]中的频率辣!
但如果我们开NNN棵树
ResultResultResult:MLEMLEMLE
要解决这个问题,我们需要尽量利用共同信息。
发现Segi−1Seg_{i-1}Segi−1和SegiSeg_{i}Segi相差的只是AiA_{i}Ai插入时经过的log2Nlog_{2}Nlog2N个节点
于是我们可以共用未修改的节点!
//////当然我们需要储存下RootiRoot_{i}Rooti以访问每棵树!!!
(应该没人不知道吧…但我太菜了还是要啪叽几句
就像这样:(图先咕着…反正也没人看 //////有人看了CallCallCall我吧
理解了就可以方便的写出代码
初始这样建树,函数的返回值为Root0Root_{0}Root0
int Build(int l,int r)
{
int now = ++cnt;
if (l < r)
{
int mid = (l + r) / 2;
L[now] = Build(l,mid);
R[now] = Build(mid + 1,r);
}
return now;
}
然后这样插入节点:
int Add_Node(int l, int r, int k, int val)
{
int now = ++cnt;
L[now] = L[k], R[now] = R[k], Sum[now] = Sum[k] + 1;
if (l == r) return now;
int mid = (l + r) / 2;
if (val <= mid) L[now] = Add_Node(l, mid, L[k], val);
else R[now] = Add_Node(mid + 1, r, R[k], val);
return now;
}
求答案时注意:如果递归到右子树要减去SSS
int Query(int Ra, int Rb, int l, int r, int rk)
{
if (l == r) return l;
int S = Sum[L[Rb]] - Sum[L[Ra]];
int mid = (l + r) / 2;
if (S >= rk) return Query(L[Ra], L[Rb], l, mid, rk);
else return Query(R[Ra], R[Rb], mid + 1, r, rk - S);
}
别忘了离散化!(注意AnsAnsAns
输出AnsQueryAns_{Query}AnsQuery就好
bool cmp(A u, A v){return u.val < v.val;}
void init()
{
for (int i = 1; i <= N; i++) a[i].val = x[i], a[i].pos = i;
std::sort(a + 1, a + N + 1, cmp);
a[0].val = -1;
for (int i = 1; i <= N; i++)
{
if (a[i].val != a[i - 1].val) RN++;
y[a[i].pos] = RN;
Ans[RN] = a[i].val;
}
}
查资料时发现@BestFy有更好的离散化写法
m = unique(b+1, b+1+n)-b-1;
NOIPNOIPNOIP可以用的黑科技!
学一学学一学
鸣谢:参考了LuoguLuoguLuogu的大佬们的题解
谢谢他们让JvRuoJvRuoJvRuo我和DalaoDalaoDalao的其他人学会了主席的树!