洛谷P3369 普通平衡树之板子

洛谷P3369题解

传送锚点

摸鱼环节

【模板】普通平衡树

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入一个数 \(x\)
  2. 删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
  3. 定义排名为比当前数小的数的个数 \(+1\)。查询 \(x\) 的排名。
  4. 查询数据结构中排名为 \(x\) 的数。
  5. \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。

对于操作 3,5,6,不保证当前数据结构中存在数 \(x\)

输入格式

第一行为 \(n\),表示操作的个数,下面 \(n\) 行每行有两个数 \(\text{opt}\)\(x\)\(\text{opt}\) 表示操作的序号($ 1 \leq \text{opt} \leq 6 $)

输出格式

对于操作 \(3,4,5,6\) 每行输出一个数,表示对应答案。

样例 #1

样例输入 #1

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

样例输出 #1

106465
84185
492737

简单分析下题目,很明显可以选择用平衡树来AC他,一般来说旋转平衡树是基础,但我不会啊FHQtreep明显更有性价比,作为20世纪的好青年,当然是什么简单写什么选择顺应时代的写法,所以这题成为了FHQtreep训练鸡。



正片开始

1. 合理建树

直接选择结构体来处理树。
咱需要:

  • l,r表示左子树和右子树;
  • val存储权值;
  • rnd存储随机权值;
  • size存储当前树的大小;
  • 写一个newnode函数存入新建点;

code:

struct 
{
    int l,r;
    int val;
    int rnd;
    int size;
}tr[N];
int root=0,n=0;
inline int _rand(){return rnd();}
int newnode(int v)
{
    tr[++n].val=v;
    tr[n].rnd=_rand();
    tr[n].size=1;
    return n;
}

2.处理更新必要部分

将左右子树更新后加上自己,很简单,但别忘了。

code:

void pushup(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}

3.核心操作之分裂合并

  1. 对于分裂,递归处理即可。勿忘更新。
void split(int p,int v,int &x,int &y)
{
    if(!p) {x=y=0;return;}
    if(tr[p].val<=v)
    {
        x=p;
        split(tr[p].r,v,tr[p].r,y);
    }
    else
    {
        y=p;
        split(tr[p].l,v,x,tr[p].l);
    }
    pushup(p);
}
  1. 对于合并操作,如果子树为空,直接相加即可。选择小根队,判断下左子树与右子树的随机权值,将较小的作为根即可。记得更新。

code:

int merge(int x,int y)
{
    if(!x||!y) {return x+y;}
    if(tr[x].rnd<tr[y].rnd)
    {
        tr[x].r=merge(tr[x].r,y);
        pushup(x);
        return x;
    }
    else
    {
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}

4. 其余操作处理

太水了,分分合合就行了

  • 增加操作,将树以v分成两个子树,建立一个新的插入点,可视作第三棵子树,然后将三棵子树合并即可。

code:

void insert(int v)
{
    int x,y;
    split(root,v,x,y);
    int z=newnode(v);
    root=merge(merge(x,z),y);
} 
  • 删除操作只需将树分成三棵子树,并将所需要删除的点的树上左子树和右子树直接合并即可。

code:

void del(int v)
{
    int x,y,z;
    split(root,v,x,z);
    split(x,v-1,x,y);
    y=merge(tr[y].l,tr[y].r);
    root=merge(merge(x,y),z);
}
  • 接下来的操作都可以参考上述方法,很容易得出结果。

code:

int rank(int v)
{
    int x,y;
    split(root,v-1,x,y);
    int ans=tr[x].size+1;
    root=merge(x,y);
    return ans;
}
int topk(int p,int k)
{
    int lsz=tr[tr[p].l].size;
    if(k==lsz+1) return tr[p].val;
    if(k<=lsz) return topk(tr[p].l,k);
    return topk(tr[p].r,k-lsz-1);
}
int get_pre(int v)
{
    int x,y;
    split(root,v-1,x,y);
    int ans=topk(x,tr[x].size);
    root=merge(x,y);
    return ans;
}
int get_suc(int v)
{
    int x,y;
    split(root,v,x,y);
    int ans=topk(y,1);
    root=merge(x,y);
    return ans;
}

函数部分到此结束。


完整代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+5;
unsigned long long seed=1;
std::mt19937 rnd(std::random_device{}());
struct fhq
{
    struct 
    {
       int l,r;
       int val;
       int rnd;
       int size;
    }tr[N];
    int root=0,n=0;
    inline int _rand(){return rnd();}
    int newnode(int v)
    {
        tr[++n].val=v;
        tr[n].rnd=_rand();
        tr[n].size=1;
        return n;
    }
    void pushup(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}
    void split(int p,int v,int &x,int &y)
    {
        if(!p) {x=y=0;return;}
        if(tr[p].val<=v)
        {
            x=p;
            split(tr[p].r,v,tr[p].r,y);
        }
        else
        {
            y=p;
            split(tr[p].l,v,x,tr[p].l);
        }
        pushup(p);
    }
    int merge(int x,int y)
    {
        if(!x||!y) {return x+y;}
        if(tr[x].rnd<tr[y].rnd)
        {
            tr[x].r=merge(tr[x].r,y);
            pushup(x);
            return x;
        }
        else
        {
            tr[y].l=merge(x,tr[y].l);
            pushup(y);
            return y;
        }
    }
    explicit fhq() { memset(tr, 0, sizeof tr); }
    int size() { return tr[root].size; }
    void insert(int v)
    {
        int x,y;
        split(root,v,x,y);
        int z=newnode(v);
        root=merge(merge(x,z),y);
    } 
    void del(int v)
    {
        int x,y,z;
        split(root,v,x,z);
        split(x,v-1,x,y);
        y=merge(tr[y].l,tr[y].r);
        root=merge(merge(x,y),z);
    }
    int rank(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=tr[x].size+1;
        root=merge(x,y);
        return ans;
    }
    int topk(int p,int k)
    {
        int lsz=tr[tr[p].l].size;
        if(k==lsz+1) return tr[p].val;
        if(k<=lsz) return topk(tr[p].l,k);
        return topk(tr[p].r,k-lsz-1);
    }
    int get_pre(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=topk(x,tr[x].size);
        root=merge(x,y);
        return ans;
    }
    int get_suc(int v)
    {
        int x,y;
        split(root,v,x,y);
        int ans=topk(y,1);
        root=merge(x,y);
        return ans;
    }
};
int main()
{
    int n,m;
    cin>>m;
    fhq t;
    int ans=0;
    while(m--)
    {
        int op,x;cin>>op>>x;
        if(op==1) t.insert(x);
        if(op==2) t.del(x);
        if(op==3) cout<<t.rank(x)<<endl;
        if(op==4) cout<<t.topk(t.root,x)<<endl;
        if(op==5) cout<<t.get_pre(x)<<endl;
        if(op==6) cout<<t.get_suc(x)<<endl;
    }
    return 0;
}


双倍经验之洛谷P6136

传送锚点

题目简述:

【模板】普通平衡树(数据加强版)

题目背景

本题是 P3369 数据加强版,扩大数据范围并增加了强制在线

题目的输入、输出和原题略有不同,但需要支持的操作相同。

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些整数,其中需要提供以下操作:

  1. 插入一个整数 \(x\)
  2. 删除一个整数 \(x\)(若有多个相同的数,只删除一个)。
  3. 查询整数 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
  4. 查询排名为 \(x\) 的数(如果不存在,则认为是排名小于 \(x\) 的最大数。保证 \(x\) 不会超过当前数据结构中数的总数)。
  5. \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。

本题强制在线,保证所有操作合法(操作 \(2\) 保证存在至少一个 \(x\),操作 \(4,5,6\) 保证存在答案)。

输入格式

第一行两个正整数 \(n,m\),表示初始数的个数和操作的个数。

第二行 \(n\) 个整数 \(a_1,a_2,a_3,\ldots,a_n\),表示初始的数

接下来 \(m\) 行,每行有两个整数 \(\text{opt}\)\(x'\)\(\text{opt}\) 表示操作的序号($ 1 \leq \text{opt} \leq 6 \(),\)x'$ 表示加密后的操作数。

我们记 \(\text{last}\) 表示上一次 \(3,4,5,6\) 操作的答案,则每次操作的 \(x'\) 都要异或\(\text{last}\) 才是真实的 \(x\)。初始 \(\text{last}\)\(0\)

输出格式

输出一行一个整数,表示所有 \(3,4,5,6\) 操作的答案的异或和

样例 #1

样例输入 #1

6 7
1 1 4 5 1 4
2 1
1 9
4 1
5 8
3 13
6 7
1 4

样例输出 #1

6

提示

样例解释

样例加密前为:

6 7
1 1 4 5 1 4
2 1
1 9
4 1
5 9
3 8
6 1
1 0

限制与约定

对于 \(100\%\) 的数据,\(1\leq n\leq 10^5\)\(1\leq m\leq 10^6\)\(0\leq a_i,x\lt 2^{30}\)

本题输入数据较大,请使用较快的读入方式。


\(\text{upd 2022.7.22}\):新增加 \(9\) 组 Hack 数据。



思路还是一模一样,这里直接给到代码。

#include <cstdio>
#include <cstring>
#include <random>
using namespace std;

const int N=1e6+1e5+1;
unsigned long long seed=1;

std::mt19937 rnd(std::random_device{}());
struct fhq
{
    struct
    {
        int l,r;
        int val;
        int rnd;
        int size;
    }tr[N];
    int root=0,n=0;
    inline int _rand(){return rnd();}
    int newnode(int v)
    {
        tr[++n].val=v;
        tr[n].rnd=_rand();
        tr[n].size=1;
        return n;
    }
    void pushup(int x){tr[x].size=tr[tr[x].l].size+tr[tr[x].r].size+1;}
    void split(int rt,int v,int &x,int &y)
    {
        if(!rt) {x=y=0;return;}
        if(tr[rt].val<=v)
        {
            x=rt;
            split(tr[rt].r,v,tr[rt].r,y);
        }
        else
        {
            y=rt;
            split(tr[rt].l,v,x,tr[rt].l);
        }
        pushup(rt);
    }
    int merge(int x,int y)
    {
        if(!x||!y) return x+y;
        if(tr[x].rnd<tr[y].rnd)
        {
            tr[x].r=merge(tr[x].r,y);
            pushup(x);
            return x;
        }
        else
        {
            tr[y].l=merge(x,tr[y].l);
            pushup(y);
            return y;  
        }
       
    }
    explicit fhq(){memset(tr,0,sizeof tr);}
    int size(){return tr[root].size;}

    void insert(int v)
    {
        int x,y;
        split(root,v,x,y);
        int z=newnode(v);
        root=merge(merge(x,z),y);
    }

    void del(int v)
    {
        int x,y,z;
        split(root,v,x,z);
        split(x,v-1,x,y);
        y=merge(tr[y].l,tr[y].r);
        root=merge(merge(x,y),z);
    }

    int rank(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=tr[x].size+1;
        root=merge(x,y);
        return ans;
    }

    int topk(int rt,int k)
    {
        int lsz=tr[tr[rt].l].size;
        if(k==lsz+1) return tr[rt].val;
        if(k<=lsz) return topk(tr[rt].l,k);
        return topk(tr[rt].r,k-lsz-1);
    }

    int get_pre(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=topk(x,tr[x].size);
        root=merge(x,y);
        return ans;
    }
    int get_suc(int v)
    {
        int x,y;
        split(root,v,x,y);
        int ans=topk(y,1);
        root=merge(x,y);
        return ans;
    }
};
int main(void)
{
    int n,m;
    scanf("%d%d",&n,&m);    
    fhq t;int w;
    for(int i=0;i<n;i++) 
    {
        scanf("%d", &w);
        t.insert(w);
    }
    int ans=0,last=0;
    while(m--)
    {
        int op,x;
        scanf("%d%d", &op, &x);
        x^=last;
        if(op==1) t.insert(x);
        if(op==2) t.del(x);
        if(op==3) {last=t.rank(x);ans^=last;}
        if(op==4) {last=t.topk(t.root,x);ans^=last;}
        if(op==5) {last=t.get_pre(x);ans^=last;}
        if(op==6) {last=t.get_suc(x);ans^=last;}
    }
    printf("%d\n", ans);
    return 0;
}

本蒟蒻只能写到这。
谢谢!

posted @ 2024-08-03 07:57  Nightmares_oi  阅读(13)  评论(6编辑  收藏  举报