CF38G Queue (区间splay)

这篇博客介绍了如何使用Splay树解决一个插队问题,其中涉及到了动态维护序列和交换位置的操作。问题设定有n个人排队,每个人有重要性和良心值,可以根据重要性调整位置,但良心值限制了最多可以交换的次数。通过Splay树的插入和旋转操作,实现了高效地插入和维护队列顺序。最终代码展示了一个简单的Splay树实现,能够正确处理插入和打印最终队列顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

题意

(摘自洛谷)
【问题描述】 有n个人依次排队,每个人都有两个属性值 a[i] 、c[i] ,a[i]是重要性值,数值越大越重要,c[i]是良心值。假如前i-1人已经排好队后,第i个人来排队,初始时他在队尾,如果他的a[i]大于排在他前面那位的重要性值,那么两人可以交换位置,每次交换良心值减1,直到他前面的人的重要性值大于a[i]或者良心值为0的时候(即最多交换c[i]次),问最终n个人的队列次序。

【输入描述】 第一行一个整数n,表示队列人数。 接下来n行,每行两个整数ai,ci,表示第i个人的重要值和良心值。所有ai是不同的。

【输出描述】 输出队列最终的结果

分析

插队问题,本质上是需要动态维护一个序列.
动态维护序列+交换位置…
很容易联想到splay的top&bottom操作(将序列中某个元素移动至最前面or最后面)

本题使用splay实现.
0.维护tr[u].maxn为u的子树的重要属性最大值.tr[u].id为该点的人的编号(答案)
1.插入元素时,从根结点开始递归,找到符合条件的叶子节点,新建.
2.每次插入之后要把该节点splay到root,(玄学优化).

关于递归插入元素:
1.如果此时待插入的人的事情重要程度小于此时递归到的节点的人(u)的重要程度,则向u的后面插入.
2.如果此时待插入的人的事情重要程度小于此时递归到的节点的右儿子的重要程度最大值(tr[tr[u].s[1]].maxn),则向u的后面插入.
3.如果此时待插入的人的良心值小于等于此时递归到的节点的右儿子的人数(tr[tr[u].s[1]].sz),则向u的后面插入.
4.其他情况,向u的前面插入.
具体代码:

void insert(int &u, int fa, int val, int c, int id)
{
    if (u == 0)
    {
        u = newnode(fa, val);
        tr[u].id = id;
        return;
    }
    if (val < tr[u].val || val < tr[tr[u].s[1]].maxn || c <= tr[tr[u].s[1]].sz)
    {
        insert(tr[u].s[1], u, val, c, id);
    }
    else
    {
        insert(tr[u].s[0], u, val, c - tr[tr[u].s[1]].sz - 1, id);
    }
    pushup(u);
}

这颗splay还算是比较简单的splay,不需要处理哨兵节点,不需要懒标记下放,不需要查询第k大.

代码

#include <bits/stdc++.h>

using namespace std;
//-----pre_def----
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
#define endl '\n'
#define init_h memset(h, -1, sizeof h), idx = 0;
#define lowbit(x) x &(-x)

//---------------
const int N = 1e5 + 10;
struct node
{
    int s[2], val, p, sz, maxn, id;
} tr[N];
int root, n, m, cnt;
void pushup(int u)
{
    tr[u].sz = tr[tr[u].s[0]].sz + tr[tr[u].s[1]].sz + 1;
    //tr[u].maxn=max(tr[u].maxn,max(tr[u]))
    int a = -1, b = -1;
    if (tr[u].s[0])
        a = tr[tr[u].s[0]].maxn;
    if (tr[u].s[1])
        b = tr[tr[u].s[1]].maxn;
    tr[u].maxn = max(max(a, b), tr[u].val);
}

void rotate(int x) //旋转
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x;
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1];
    tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y;
    tr[y].p = x;
    pushup(y), pushup(x);
}
void splay(int x, int k)
{
    while (tr[x].p != k)
    {
        int y = tr[x].p, z = tr[y].p;
        if (z != k) //x的祖宗还不是k
        {
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) //异或,当zyx不为一条直链的时候
            {
                rotate(x);
            }
            else
            {
                rotate(y);
            }
        }
        rotate(x);
    }
    pushup(x);
    if (!k)
        root = x;
}
int newnode(int father, int val)
{
    tr[++cnt] = {0, 0, val, father, 1, val};
    return cnt;
}
void insert(int &u, int fa, int val, int c, int id)
{
    if (u == 0)
    {
        u = newnode(fa, val);
        tr[u].id = id;
        return;
    }
    if (val < tr[u].val || val < tr[tr[u].s[1]].maxn || c <= tr[tr[u].s[1]].sz)
    {
        insert(tr[u].s[1], u, val, c, id);
    }
    else
    {
        insert(tr[u].s[0], u, val, c - tr[tr[u].s[1]].sz - 1, id);
    }
    pushup(u);
}
void print(int u = root)
{
    if (tr[u].s[0])
        print(tr[u].s[0]);
    printf("%d ", tr[u].id);
    if (tr[u].s[1])
        print(tr[u].s[1]);
}
void init() {}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    int StartTime = clock();
#endif
    scanf("%d", &n);
    fir(i, 1, n)
    {
        int a, c;
        scanf("%d%d", &a, &c);
        insert(root, 0, a, c, i);
        splay(cnt, 0);
    }
    print();
    puts("");
#ifndef ONLINE_JUDGE
    printf("Run_Time = %d ms\n", clock() - StartTime);
#endif
    return 0;
}

把一个u打成了n居然能过67个点…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值