首页 > 其他分享 >[lnsyoj285/luoguP2596/ZJOI2006]书架

[lnsyoj285/luoguP2596/ZJOI2006]书架

时间:2024-07-07 14:31:51浏览次数:17  
标签:lnsyoj285 int tr luoguP2596 分裂 key ZJOI2006 节点 size

题意

维护一个长度为 \(n\) 的序列 \(a\),进行 \(m\) 次操作,操作包括:

  1. 将 \(x\) 放置于序列开头;
  2. 将 \(x\) 放置于序列末尾;
  3. 将 \(x\) 与其前驱/后继交换;
  4. 查询 \(x\) 的下标 \(-1\);
  5. 查询下标为 \(x\) 的数

sol

维护序列,可以使用线段树或平衡树,本题使用平衡树更为简便。
介于已经学习过 Splay,本题使用 FHQ-Treap 解决。
(fhq佬%%%)
FHQ-Treap 通过将 BST 分裂与合并实现操作,且分裂与合并后该 BST 的 \(val\) 仍满足堆的性质。

分裂(Split)

FHQ-Treap 拥有两种分裂方式:按值分裂与按排名分裂
按值分裂时,会给定 \(qkey\),所有 \(\le qkey\) 的点都会被分裂至树 \(x\),所有 \(> qkey\) 的点都会被分裂至树 \(y\)。
根据 BST 的定义,如果根节点的 \(key \le qkey\),说明根节点的左子树的所有节点都 \(\le key\),因此将根节点作为 \(x\) 的根,然后递归处理;同理,如果根节点的 \(key > qkey\),说明根节点的右子树的所有节点都 \(> key\),因此将根节点作为 \(y\) 的根,然后递归处理。
按值分裂适用于处理集合式问题,例如[lnsyoj118/luoguP3369]普通平衡树

代码

void split(int u, int key, int &x, int &y){
    if (!u) {
        x = y = 0;
        return ;
    }
    if (tr[u].key <= key) x = u, split(tr[u].r, key, tr[x].r, y);
    else y = u, split(tr[u].l, key, x, tr[y].l);
    pushup(u);
}

按排名分裂时,与按值分裂基本相同,但需要注意的是,如果递归遍历至右子树,查找的排名需要在原有基础上减去 \(lson.size+1\),即左子树和根节点中的节点数量
按排名分裂适用于处理序列式问题,例如本题和[lnsyoj119/luoguP3391]文艺平衡树
注意:按排名分裂调用 split 时,\(size\) 应为该树中的排名,而非序列中的排名
代码

void split(int u, int size, int &x, int &y){
    if (!u) {
        x = y = 0;
        return ;
    }
    if (tr[tr[u].l].size + 1 <= size) x = u, split(tr[u].r, size - tr[tr[u].l].size - 1, tr[x].r, y);
    else y = u, split(tr[u].l, size, x, tr[y].l);
    pushup(u);
}

合并(Merge)

由于合并后仍需要保持 \(val\) 二叉堆的性质,因此合并时需要依照 \(val\) 进行合并
具体地(以小根堆为例),如果树 \(x\) 根节点的\(val\)更小,则将树 \(x\) 根节点作为合并后树的根节点,其左子树即为树 \(x\) 的左子树,右子树为将树 \(x\) 的右子树与树 \(y\) 合并后得到的树;同理,如果树 \(y\) 根节点的\(val\)更小,则将树 \(y\) 根节点作为合并后树的根节点,其右子树即为树 \(y\) 的右子树,左子树为将树 \(y\) 的左子树与树 \(x\) 合并后得到的树。
代码

int merge(int x, int y){
    if (!x || !y) return x | y;
    if (tr[x].val <= tr[y].val) {
        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;
    }
}

值得注意的是,在进行分裂和合并操作之后,都需要进行上推操作来维护 \(size\)(和父节点)

说回本题。FHQ-Treap 并不支持根据 \(key\) 查询节点在序列中的位置,因此需要记录 \(pos\),来记录 \(key\) 对应的节点编号,再根据节点编号和父亲上推。具体操作是:根据BST的定义,该节点的左子树一定比该节点小,同时,当该节点是父节点的右子树时,父节点和左子树也一定比该节点小。
代码如下:

int get_rank(int u){
    int res = tr[tr[u].l].size + 1;
    while (u != root){
        if (tr[tr[u].fa].r == u) res += tr[tr[tr[u].fa].l].size + 1;
        u = tr[u].fa;
    }
    return res;
}

剩余的就是分裂与合并操作的组合了。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>

using namespace std;

const int N = 80005;

struct Node{
    int l, r;
    int key, val;
    int size;
    int fa;
}tr[N];

int idx;
int root;
int pos[N];
int n, m;

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

int create(int key){
    tr[ ++ idx].key = key;
    tr[idx].val = rand();
    tr[idx].size = 1;
    pos[key] = idx;
    return idx;
}

void split(int u, int size, int &x, int &y){
    if (!u) {
        x = y = 0;
        return ;
    }
    if (tr[tr[u].l].size + 1 <= size) x = u, split(tr[u].r, size - tr[tr[u].l].size - 1, tr[x].r, y);
    else y = u, split(tr[u].l, size, x, tr[y].l);
    pushup(u);
}

int merge(int x, int y){
    if (!x || !y) return x | y;
    if (tr[x].val <= tr[y].val) {
        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;
    }
}

int get_rank(int u){
    int res = tr[tr[u].l].size + 1;
    while (u != root){
        if (tr[tr[u].fa].r == u) res += tr[tr[tr[u].fa].l].size + 1;
        u = tr[u].fa;
    }
    return res;
}

int get_key(int rank){
    int u = root;
    while (u){
        if (tr[tr[u].l].size >= rank) u = tr[u].l;
        else if (tr[tr[u].l].size + 1 == rank) return tr[u].key;
        else rank -= tr[tr[u].l].size + 1, u = tr[u].r;
    }
    return -1;
}

int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ){
        int t;
        scanf("%d", &t);
        root = merge(root, create(t));
    }

    int r1, r2, r3, r4;
    while (m -- ){
        r1 = r2 = r3 = r4 = 0;
        char op[10];
        int x;
        scanf("%s%d", op, &x);
        if (*op == 'T'){
            int p = get_rank(pos[x]);
            split(root, p - 1, r1, r2);
            split(r2, 1, r2, r3);
            root = merge(r2, merge(r1, r3));
        } else if (*op == 'B'){
            int p = get_rank(pos[x]);
            split(root, p - 1, r1, r2);
            split(r2, 1, r2, r3);
            root = merge(merge(r1, r3), r2);
        } else if (*op == 'I'){
            int t;
            scanf("%d", &t);
            if (!t) continue;
            int p = get_rank(pos[x]);
            if (t == 1){
                split(root, p - 1, r1, r2);
                split(r2, 1, r2, r3);
                split(r3, 1, r3, r4);
                root = merge(r1, merge(r3, merge(r2, r4)));
            } else{
                split(root, p - 2, r1, r2);
                split(r2, 1, r2, r3);
                split(r3, 1, r3, r4);
                root = merge(r1, merge(r3, merge(r2, r4)));
            }
        } else if (*op == 'A'){
            printf("%d\n", get_rank(pos[x]) - 1);
        } else if (*op == 'Q'){
            printf("%d\n", get_key(x));
        }
    }

    return 0;
}

标签:lnsyoj285,int,tr,luoguP2596,分裂,key,ZJOI2006,节点,size
From: https://www.cnblogs.com/XiaoJuRuoUP/p/18287507/lnsyoj285_luoguP2596

相关文章

  • P2585 [ZJOI2006] 三色二叉树
    原题链接总结1.要学会动态规划这种思维方式,即定义状态和状态之间的转移2.本题的难点在于如何将抽象的输入数据转换成树状结构处理和定义状态,这个定义状态让我想到了初中添加几何线,可能要多做题才能有感觉吧3.有一定模拟的部分,这一部分要细心\(Code\)#include<bits/stdc++.h>......
  • P2596 [ZJOI2006]书架 题解
    题目传送门:link。FHQ-Treap解题的关键在于如何来求出一本书上面有多少本书,但考虑到我们里面没有像权值一样的东西来让我们用按值分裂来完成这个操作,所以考虑用按排名分裂来实现。我们按照先后顺序把所有的书都给插入到平衡树里面,根据二叉搜索树的性质,当前结点的所有左子树的结......
  • Luogu1772 [ZJOI2006] 物流运输
    传送门简化题意给你$m$个码头,码头之间有双向边连接,$n$天,其中一些码头在某些天会不可用,这$n$天都要有一条从$1$到$m$的路,每一次更换道路会需要$k$的代价,求这$n$天每天从$1$到$m$的距离之和与更改道路的价值之和的最小值。Solution首先我们能想到一个贪心的策......
  • P2596 [ZJOI2006]书架
    \(\color{purple}\text{P2596[ZJOI2006]书架}\)解题方法考虑使用\(\text{FHQ}\)平衡树,我们只使用编号,而不使用权值,平衡树上的先序遍历即为书的放置顺序。\(\text{Query}\):这是最简单的操作,直接查询即可。\(\text{Ask}\):本题的核心,我们知道编号,因为没有权值,没法从根往下......
  • P1772 [ZJOI2006] 物流运输
    P1772[ZJOI2006]物流运输题意:需要将物品从\(A\)移动到\(B\),需要\(n\)天才能运输完成,运输过程中需要转停很多个码头。求指定一个\(n\)天的运输计划,使得总成本......
  • Luogu P1772 [ZJOI2006]物流运输
    题目链接:​​传送门​​很麻烦也很难想的一道题数据很小大胆yy详细解释在代码里#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<co......
  • 做题记录整理dp15 P1772. [ZJOI2006] 物流运输(2022/9/28)
    P3648[APIO2014]序列分割第一次做斜率优化的题题解看懂了,但没有完全看懂等以后得回来看一次#include<bits/stdc++.h>#definefor1(i,a,b)for(inti=a;i<=b;i++)......
  • P1772 [ZJOI2006] 物流运输
    #include<bits/stdc++.h>usingnamespacestd;constintMAXN=1e8;classsolve{ public: intn,m,k,E; priority_queue<pair<int,int>>q; structnode{......
  • 做题记录整理dp15 P1772. [ZJOI2006] 物流运输(2022/9/28)
    P1772.[ZJOI2006]物流运输图论+dp首先看数据范围这么小,其实就可以猜到很可能是先把i到j天的最短路都求出来然后就会发现dp方程很简单了dp[i]=min(dp[j]+最短路[j+1][......
  • luogu P1772 [ZJOI2006] 物流运输 (dp, 最短路)
    https://www.luogu.com.cn/problem/P1772虽然是图论背景,但是1-n天之间是线性关系。没法贪心决策,考虑dp:我本来写的dp是i-1转移到i,但是这样没法处理哪一天能走哪些最短路......