动态区间第K小
动态区间第K小,又称带修改主席树(动态主席树),对于初学真的不太友好。因为这个和静态区间第K小(静态主席树)数据结构都完全不同了。
和求静态第K小一样,我们要得到[L, R]状态的线段树,才能进行二分得到第K小。
静态主席树 是一个 可持久化线段树,每次利用 前缀和来计算[L, R]状态,但是前缀和就不便于进行修改操作了,那么对于可修改的区间求和,我们就要用到 树状数组/线段树。
这里就是要采用 树套树->
外层 是 树状数组/线段树:即区间线段树,每一个结点都存储 当前位置区间 的 一棵主席树(即主席树的根结点)
内层 是 主席树:即权值线段树,每个结点存储 当前权值区间 的 出现次数
所以外层主要用来 锁定位置(就是树状数组/线段树求区间和的过程),由于这里的求区间和每个元素是主席树,不能直接相加,那么就要保存所有求得[L,R]状态需要的主席树(的根结点),然后同时遍历,对应的点求和,求得[L, R]的状态。
具体可以看这篇blog(写的真的好):https://www.cnblogs.com/LiuRunky/p/Sustainable_Segment_Tree.html
光看文字说明其实挺难理解的(这东西真的很难用文字说清),看了代码会更好理解。
但由于个人写惯了线段树,对树状数组不是很熟悉,导致看代码理解的过程十分坎坷(找到全是树状数组+主席树)
于是这里打算把两种形式都写一下。(不过还是推荐树状数组,空间小,易编写,无需递归)
此外,注意:离散化不仅要对原序列,对修改的值也要一同离散化,所以要先把所有修改操作先读入,存储起来,离散化以后再开始各条操作。修改时,要先令原值出现次数-1,再令新值出现次数+1。
模板题:LUOGU - P2617 Dynamic Rankings
①线段树套主席树
时限2s,但是40%的点超时了0.2s左右,在尝试各种卡常优化的排列组合后依旧有15%的点TLE(有些卡常甚至更慢了,这东西也太玄学了)
综合考虑可能是外层用线段树,更新和定位时递归调用时间产生的时间消耗(树状数组就不需要递归了),也有可能是测试数据正好对这种写法不友好吧…
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+50;
int n,m,a[maxn];
int root[maxn<<2],t[maxn*400],ls[maxn*400],rs[maxn*400],cnt=0;
int b[2*maxn],N=0;
struct
{
int opt,i,j,k;
}q[maxn];
void init()
{
sort(b+1,b+N+1);
N=unique(b+1,b+N+1)-(b+1);
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+N+1,a[i])-b;
for(int i=1;i<=m;i++)
{
if(q[i].opt)
q[i].k=lower_bound(b+1,b+N+1,q[i].k)-b;
}
}
void updata_in(int &rt,int l,int r,int x,int val)
{
//内层更新,对应权值出现次数+1/-1
if(!rt)
rt=++cnt;
if(l==r)
{
t[rt]+=val;
return;
}
int mid=(l+r)>>1;
if(x<=mid)
updata_in(ls[rt],l,mid,x,val);
else
updata_in(rs[rt],mid+1,r,x,val);
t[rt]=t[ls[rt]]+t[rs[rt]];
}
void updata_out(int rt,int l,int r,int pos,int x,int val)
{
//外层更新,找到logN个插入位置
updata_in(root[rt],1,N,x,val);
if