您的位置:首页 > 其它

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

2018-01-22 15:37 246 查看

题目

  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
, tim
;
char c;
string s, r
;
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;
}
}

//删除

b4a6
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Treap