主席树 —— ③动态区间第K小(树套树,带修改主席树)

动态区间第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
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值