codevs1985: GameZ游戏排名系统(Treap)

本文介绍如何使用Treap数据结构设计一个游戏排名系统,处理上传得分、查询排名和获取排名区间内玩家信息的请求。Treap结合了二叉搜索树和堆的特性,保证了操作的高效性,时间复杂度为O(logn)。

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

题目

  GameZ为他们最新推出的游戏开通了一个网站。世界各地的玩家都可以将自己的游戏得分上传到网站上。这样就可以看到自己在世界上的排名。得分越高,排名就越靠前。当两个玩家的名次相同时,先上传记录者优先。由于新游戏的火爆,网站服务器已经难堪重负。为此GameZ雇用了你来帮他们重新开发一套新的核心。 排名系统通常要应付三种请求:上传一条新的得分记录、查询某个玩家的当前排名以及返回某个区段内的排名记录。当某个玩家上传自己最新的得分记录时,他原有的得分记录会被删除。为了减轻服务器负担,在返回某个区段内的排名记录时,最多返回10条记录。

输入描述

  第一行是一个整数n(n>=10)表示请求总数目。
  接下来n行每行包含了一个请求。请求的具体格式如下:
    +Name Score 上传最新得分记录。Name表示玩家名字,由大写英文字母组成,不超过10个字符。Score为不超过无符号32位整型表示范围的非负整数。
    ?Name 查询玩家排名。该玩家的得分记录必定已经在前面上传。
    ?Index 返回自第Index名开始的最多10名玩家名字。Index必定合法,即不小于1,也不大于当前有记录的玩家总数。
  20%数据满足N<=100
  100%数据满足N<=250000
  时间限制为2s
  输入数据大小不超过2M。
  NOTE:用C++的fstream读大规模数据的效率较低。

输出描述

对于每条查询请求,输出相应结果。对于?Name格式的请求,应输出一个整数表示该玩家当前的排名。对于?Index格式的请求,应在一行中依次输出从第Index名开始的最多10名玩家姓名,用一个空格分隔。

样例输入

20
+ADAM 1000000
+BOB 1000000
+TOM 2000000
+CATHY 10000000
?TOM
?1
+DAM 100000
+BOB 1200000
+ADAM 900000
+FRANK 12340000
+LEO 9000000
+KAINE 9000000
+GRACE 8000000
+WALT 9000000
+SANDY 8000000
+MICK 9000000
+JACK 7320000
?2
?5
?KAINE

样例输出

2
CATHY TOM ADAM BOB
CATHY LEO KAINE WALT MICK GRACE SANDY JACK TOM BOB
WALT MICK GRACE SANDY JACK TOM BOB ADAM DAM
4

分析

题目中共有三种操作, 而n最大可达250000, 所以这三张操作的时间复杂度应不劣于O(logn), 可以考虑用Treap来实现. Treap是什么? Treap时一种平衡树. Treap = Trea + Heap, 顾名思义, Treap把BST(二叉查找树)和Heap结合了起来, 它和BST一样满足许多优美的性质, 而引入堆的目的就是为了维护平衡, 具有简明易懂, 易于编写, 稳定性佳等优点.[1]
下图是Treap和其他平衡树的比较:
这里写图片描述
在这里不赘述Treap的具体实现, 只讨论Treap的应用.
在这道题中, Treap的结点应该保存有名字, 分数, 时间戳, 子树大小这些信息.
对于第一种操作+Name Score, 先删除原有的结点, 再插入新的结点, 这是平衡树的基本操作, 时间复杂度为O(logn).
对于第二种操作?Name, 直接查找, 时间复杂度也为O(logn).
对于第三种操作?Index, 题目说明了最多只需要输出10位玩家的名字, 即查找排名在[index, min(index+9, all] 区间内的玩家. 我们在结点中已经保存了子树大小这一信息, 于是也可以O(logn)完成这一操作.
具体实现看代码.

代码

#include<bits/stdc++.h>
#define x first
#define y second
#define ok puts("ok");
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const double PI = acos(-1.0);
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int N=3e5+9;
const int shift=1e3+9;
const double Eps=1e-7;

int n, tot, timer, score, sco[N], tim[N];
char c;
string s, r[N];
map<string, int> m;

//Treap结点
struct node {
    int id, score, timer, fix, size;    //字符串映射id, 分数, 时间戳, 修正值, 子树大小
    node *left, *right;                 //左右子树
    inline int lsize() {return left?left->size:0;}      //求左子树大小
    inline int rsize() {return right?right->size:0;}    //求右子树大小
};

//左旋
void leftRotate(node *&a) {
    node *b = a->right;     //与BST的左旋操作相似
    a->right = b->left;
    b->left = a;
    a = b;
    b = a->left;
    b->size = b->lsize() + b->rsize() + 1;  //修正经过旋转操作后受影响的结点的子树大小
    a->size = a->lsize() + a->rsize() + 1;
}

//右旋
void rightRotate(node *&a) {
    node *b = a->left;      //与BST的右旋操作相似
    a->left = b->right;
    b->right = a;
    a = b;
    b = a->right;
    b->size = b->lsize() + b->rsize() + 1;  //修正经过旋转操作后受影响的结点的子树大小
    a->size = a->lsize() + a->rsize() + 1;
}

//插入
void insert(node *&p, int id, int score, int timer) {
    if(!p) {
        p = new node;
        p->id = id;
        p->score = score;
        p->timer = timer;
        p->fix = rand();        //用随机函数生成修正值
        p->left = p->right = NULL;
        p->size = 1;
    }
    else if(score <= p->score) {
        insert(p->left, id, score, timer);
        if(p->left->fix < p->fix)   //维护堆的特性, 从而保证BST的平衡, 该题维护的是最小堆, 换成最大堆也是OK的
            rightRotate(p);
        else
            p->size = p->lsize() + p->rsize() + 1;
    }
    else {
        insert(p->right, id, score, timer);
        if(p->right->fix < p->fix)  //维护堆的特性, 从而保证BST的平衡
            leftRotate(p);
        else
            p->size = p->lsize() + p->rsize() + 1;
    }
}

//删除
void del(node *&p, int score, int timer) {
    if(p->score == score && p->timer == timer) { //找到了待删除结点
        if(!p->left || !p->right) { //假如是叶子结点或者链结点(只有一个儿子的结点), 直接删除
            node *t = p;
            if(!p->right)
                p=p->left;
            else
                p=p->right;
            delete t;
        }
        else {
            if(p->left->fix < p->right->fix) {  //假如左儿子的修正值小于右儿子的修正值, 进行右旋
                rightRotate(p);
                del(p->right, score, timer);
                p->size = p->lsize() + p->rsize() + 1;
            }
            else {                              //假如左儿子的修正值大于右儿子的修正值, 进行左旋
                leftRotate(p);
                del(p->left, score, timer);
                p->size = p->lsize() + p->rsize() + 1;
            }
        }
    }
    else if(score < p->score || (score == p->score && timer > p->timer)) {  //待删除结点在p的左子树中
        del(p->left, score, timer);
        p->size = p->lsize() + p->rsize() + 1;
    }
    else {      //待删除结点在p的右子树中
        del(p->right, score, timer);
        p->size = p->lsize() + p->rsize() + 1;
    }
}

//查找排在第k的玩家
int findK(node *&p, int k) {
    if(k < p->lsize() + 1)
        return findK(p->left, k);
    else if(k > p->lsize() + 1)
        return findK(p->right, k-(p->lsize()+1));
    return p->id;
}

//查找分数为score且时间戳为timer的玩家
int search(node *&p, int score, int timer, int rank) {
    if(p->score == score && p->timer == timer)
        return rank;
    else if(score < p->score || (score == p->score &&timer > p->timer))
        return search(p->left, score, timer, rank - p->left->rsize() - 1);
    else
        return search(p->right, score, timer, rank + p->right->lsize() + 1);
}

int main(void) {
    node *root = NULL;
    int n; cin >> n;
    while(n--) {
        getchar();
        c = getchar();
        if(c == '+') {
            cin >> s >> score;
            if(m[s]) {  //假如该玩家之前已上传过分数
                int id = m[s];
                del(root, sco[id], tim[id]);        //先删除
                insert(root, id, score, ++timer);   //后插入
                sco[id] = score, tim[id] = timer;   //记录分数和时间戳, 作为查找操作的信息
            }
            else {      //假如该玩家首次上传分数
                m[s] = ++tot;
                r[tot] = s;
                insert(root, tot, score, ++timer);
                sco[tot] = score, tim[tot] = timer;
            }
        }
        else {
            cin>>s;
            if(isdigit(s[0])) {
                int all = root->lsize() + root->rsize() + 1, t = 0, k = 1;
                for(int i = s.length()-1; i >= 0; i--) {
                    t += (s[i] - '0') * k;
                    k *= 10;
                }
                for(int i = t; i <= min(t+9, all); i++) {
                    int t1 = findK(root, all-i+1);  //二叉树中查找排名第1的, 其实是排在倒数第1, 所以需要反过来
                    if(i == min(t+9, all))
                        cout << r[t1] << endl;
                    else
                        cout << r[t1] << ' ';
                }
            }
            else {
                int id = m[s];
                int ans = search(root, sco[id], tim[id], root->lsize() + 1);
                int all = root->lsize() + root->rsize() + 1;
                cout << all-ans+1 << endl;
            }
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值