HDU 3436 Queue-jumpers splay 离散化 模拟

题目地址:

http://acm.hdu.edu.cn/showproblem.php?pid=3436

题意:

一个序列有 N(1<=N<=1e8) 个人,起初编号为1的人在序列的第1个位置,2在第2个位置……对这个序列有 Q(1<=Q<=1e5) 次操作,操作类型如下:

  • Top x:把编号为x的人放到序列的最前面
  • Query x:询问编号为x的人目前在序列的位置
  • Rank x:询问目前在序列第x个位置的人的编号

思路:

比较基础的splay吧,就是数据范围过大,所以要先离散化,Top操作的方法是把x这个人删去,放到splay的最前面,Query则是用数组查找x这个人对应节点的编号,再得到该节点的在splay的位置,Rank只需要找到splay的第x个节点,输出相应值就好了。
可见Top,Query操作的x都是需要被单独拿出来的,其他的数字一段一段表示也没有问题,离散化时就依Top和Query中的x把数轴分段。
虽然splay是排序二叉树,但里面存的数值不需要是真正的有序,splay的操作使其始终符合排序二叉树的性质,然后我们可以利用这些性质完成一些模拟。
这是自己做的第一道splay,所以代码不是和我模板 里的代码完全一样。
额外注意一下,top() 函数如果不在最后一步把新节点翻转至根的话,就会T(有一组数据就是不断进行Top操作,很容易让没有这个操作的代码TLE)
由此见维护splay树的形状是一件很重要的事
还有删节点要把这个节点先旋转到根节点再删的原因,因为在根节点删除的话,就只需要pushup(root) 一次,如果不是在根节点的话,就要不断pushup更新整棵splay树,代价和把节点旋转到根节点差不多,代码却比只删根的策略来的复杂

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define MS(x, y) memset(x, y, sizeof(x))
#define lson l, m - 1
#define rson m + 1, r

const int MAXN = 1e5 + 5;
const int MAXM = MAXN << 1;

int s[MAXM], e[MAXM], cnt;
int node[MAXM];
int root, tot;
int siz[MAXM], data[MAXM], pre[MAXM], son[MAXM][2], num[MAXM];
char str[10];
int op[MAXN], par[MAXN];
int show_num[MAXN];
int n, q;

inline void init() {
    tot = 1;
//    son[root][0] = son[root][1] =
//        pre[root] = siz[root] =
//        num[root] = data[root] = 0;
}

int newnode(int k, int fa) {
    pre[tot] = fa;
    siz[tot] = e[k] - s[k] + 1;
    num[tot] = siz[tot];
    data[tot] = k;
    node[k] = tot;
    MS(son[tot], 0);
    return tot++;
}

inline void pushup(int x) {
    siz[x] = siz[son[x][0]] + siz[son[x][1]] + num[x];
}

int build(int fa, int l ,int r) {
    if (l > r) return 0;
    int m = (l + r) >> 1;
    int x = newnode(m, fa);
    son[x][0] = build(x, lson);
    son[x][1] = build(x, rson);
    pushup(x);
    return x;
}

void Rotate(int x, int kind) {
    int y = pre[x];
    son[y][!kind] = son[x][kind];
    pre[son[x][kind]] = y;
    if (pre[y]) {
        son[pre[y]][son[pre[y]][1] == y] = x;
    }
    pre[x] = pre[y];
    son[x][kind] = y;
    pre[y] = x;
    pushup(y);
}

void splay(int x, int goal) {
    while (pre[x] != goal) {
        if (pre[pre[x]] == goal) Rotate(x, son[pre[x]][0] == x);
        else {
            int y = pre[x];
            int kind = (son[pre[y]][0] == y);
            if (son[y][kind] == x) {
                Rotate(x, !kind);
                Rotate(x, kind);
            } else {
                Rotate(y, kind);
                Rotate(x, kind);
            }
        }
    }
    pushup(x);
    if (goal == 0) root = x;
}

int Find(int val) {
    int l = 0, r = cnt - 1, mid;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (s[mid] <= val && val <= e[mid]) return mid;
        if (e[mid] < val) l = mid + 1;
        else r = mid - 1;
    }
}

int get_min(int x) {
    while (son[x][0]) {
        x = son[x][0];
    }
    return x;
}

void pushfront(int &r, int k, int fa) {
    if (r == 0) {
        r = newnode(k, fa);
        return ;
    }
    pushfront(son[r][0], k, r);
    pushup(r);
}

void deleteroot() {
    if (!son[root][0] || !son[root][1]) {
        root = son[root][0] + son[root][1];
        pre[root] = 0;
        return ;
    }
    int k = get_min(son[root][1]);
    splay(k, root);
    son[k][0] = son[root][0];
    pre[son[root][0]] = k;
    root = k;
    pre[root] = 0;
    pushup(root);
}

void top(int x) {
    int k = Find(x);
    int y = node[k];
    splay(y, 0);
    deleteroot();
    pushfront(root, k, 0);
    splay(tot - 1, 0);
}

int get_rank(int val) {
    int k = Find(val);
    int y = node[k];
    splay(y, 0);
    return siz[son[root][0]] + 1;
}

int get_kth(int k, int x = root) {
    int t = siz[son[x][0]];
    if (k <= t) return get_kth(k, son[x][0]);
    if (k <= t + num[x]) return s[data[x]] + k - t - 1;
    return get_kth(k - t - num[x], son[x][1]);
}

int main() { //freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);
    int T, kase = 0;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &q);
        int tol = 0;
        show_num[tol++] = 0;
        for (int i = 0; i < q; ++i) {
            scanf("%s%d", str, par + i);
            if (str[0] == 'T') op[i] = 1;
            else if (str[0] == 'Q') op[i] = 2;
            else op[i] = 3;
            if (op[i] < 3) show_num[tol++] = par[i];
        }
        show_num[tol++] = n;
        sort(show_num, show_num + tol);
        cnt = 0;
        for (int i = 1; i < tol; ++i) if (show_num[i] != show_num[i - 1]) {
            if (show_num[i] - show_num[i - 1] > 1) {
                s[cnt] = show_num[i - 1] + 1;
                e[cnt] = show_num[i] - 1;
                ++cnt;
            }
            s[cnt] = show_num[i];
            e[cnt] = show_num[i];
            ++cnt;
        }
        init();
        root = build(0, 0, cnt - 1);
        printf("Case %d:\n", ++kase);
        for (int i = 0; i < q; ++i) {
            if (op[i] == 1) top(par[i]);
            else if (op[i] == 2) cout<<get_rank(par[i])<<endl;
            else cout<<get_kth(par[i])<<endl;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值