首页 > 代码库 > splay专题复习——bzoj 3224 & 1862 & 1503 题解
splay专题复习——bzoj 3224 & 1862 & 1503 题解
【前言】快要省选二试了。上次去被虐出翔了~~这次即便是打酱油,也要打出风采!于是暂停新东西的学习,然后开始复习以前的知识,为骗分做准备。PS:区间翻转的暂时跳过,就算学了也来不及巩固了。
【BZOJ3224】
3224: Tyvj 1728 普通平衡树
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1477 Solved: 570
Description
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)
Input
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
Output
对于操作3,4,5,6每行输出一个数,表示对应答案
Sample Input
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
Sample Output
84185
492737
【分析】写了半个下午,调了一个晚上,这是多么的壮烈啊。
splay的基本操作。因为刚开始复习,这一段的rotate和splay直接copy模板的咕~~(╯﹏╰)b。
对于相同的数,处理起来很麻烦。YD表示可以用一个count[x]记录x出现的次数。而我是傻的,直接插入进去。这样的话,我还记录了num[i][w],表示以i节点的左右子树的大小。(只需在rotate的时候改变一下即可)
查询K的排名时,只需把K再插入一遍(<=的形式插),再旋转至根,答案就是num[k][1]+1。
还有,有关前驱和后继真是烦人(万一有重复!)。对此,YY教了我一个乱使的方法。每次对于K求出它的前驱(后继)P,如果P<K结束,输出P,否则把P提到根,再做类似的操作。
【代码】
/************************************************************** Problem: 3224 User: jiangshibiao Language: C++ Result: Accepted Time:696 ms Memory:7080 kb ****************************************************************/ #include<cstdio> #define N 200005 using namespace std; int son[N][3],num[N][3],f[N],a[N]; int opt,n,i,root,x,node,ans,flag; inline void Rotate(int x,int w)//1:左旋;2:右旋 { int y=f[x],z=f[y]; son[y][3-w]=son[x][w]; if (son[x][w]) f[son[x][w]]=y; f[x]=z; num[y][3-w]=num[x][w]; num[x][w]+=num[y][w]+1; if (z) { if (y==son[z][1]) son[z][1]=x; else son[z][2]=x; } f[y]=x;son[x][w]=y; } inline void splay(int x) { int y; while (f[x]) { y=f[x]; if (!f[y]) { if (x==son[y][1]) Rotate(x,2);else Rotate(x,1); continue; } if (y==son[f[y]][1]) { if (x==son[y][1]) Rotate(y,2),Rotate(x,2); else Rotate(x,1),Rotate(x,2); } else { if (x==son[y][2]) Rotate(y,1),Rotate(x,1); else Rotate(x,2),Rotate(x,1); } } root=x; } inline void insert(int x,int add) { if (add<=a[x]) { if (!son[x][1]) son[x][1]=++node,a[node]=add,f[node]=x; else insert(son[x][1],add); num[x][1]++; } else { if (!son[x][2]) son[x][2]=++node,a[node]=add,f[node]=x; else insert(son[x][2],add); num[x][2]++; } } inline void del(int x,int add) { if (!x) return; if (add==a[x]) { splay(x); if (!son[x][1]&&!son[x][2]){root=0;return;} if (!son[x][1]) {root=son[x][2];f[son[x][2]]=0;return;} if (!son[x][2]) {root=son[x][1];f[son[x][1]]=0;return;} int find=son[x][1],temp=son[x][2]; while (son[find][2]) find=son[find][2]; splay(find);son[find][2]=temp;f[temp]=find; return; } if (add<a[x]) del(son[x][1],add);else del(son[x][2],add); } inline int Kth(int x,int add,int now) { insert(root,add);splay(node); int ans=num[node][1]+1; del(node,add); return ans; } inline int Ask(int x,int k) { if (k==num[x][1]+1) return a[x]; if (k<=num[x][1]) return Ask(son[x][1],k); return Ask(son[x][2],k-num[x][1]-1); } inline int pred(int x,int k) { int find=son[x][1];if (!find) return 0; while (son[find][2]) find=son[find][2]; if (a[find]!=k) flag=1;return find; } inline int succ(int x,int k) { int find=son[x][2];if (!find) return 0; while (son[find][1]) find=son[find][1]; if (a[find]!=k) flag=1;return find; } int main() { scanf("%d",&n);root=0; for (i=1;i<=n;i++) { scanf("%d%d",&opt,&x); if (opt==1) insert(root,x),splay(node); if (opt==2) del(root,x); if (opt==3) printf("%d\n",Kth(root,x,0)); if (opt==4) printf("%d\n",Ask(root,x)); if (opt==5) { insert(root,x);splay(node); for (flag=0,ans=pred(root,x);!flag||!ans;splay(ans),ans=pred(root,x)); del(root,x);printf("%d\n",a[ans]); } if (opt==6) { insert(root,x);splay(node); for (flag=0,ans=succ(root,x);!flag||!ans;splay(ans),ans=succ(root,x)); del(root,x);printf("%d\n",a[ans]); } } return 0; }
【BZOJ1056/1862】
1862: [Zjoi2006]GameZ游戏排名系统
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 554 Solved: 212
Description
GameZ为他们最新推出的游戏开通了一个网站。世界各地的玩家都可以将自己的游戏得分上传到网站上。这样就可以看到自己在世界上的排名。得分越高,排名就越靠前。当两个玩家的名次相同时,先上传记录者优先。由于新游戏的火爆,网站服务器已经难堪重负。为此GameZ雇用了你来帮他们重新开发一套新的核心。排名系统通常要应付三种请求:上传一条新的得分记录、查询某个玩家的当前排名以及返回某个区段内的排名记录。当某个玩家上传自己最新的得分记录时,他原有的得分记录会被删除。为了减轻服务器负担,在返回某个区段内的排名记录时,最多返回10条记录。
Input
第一行是一个整数n(n>=10)表示请求总数目。接下来n行每行包含了一个请求。请求的具体格式如下: +Name Score 上传最新得分记录。Name表示玩家名字,由大写英文字母组成,不超过10个字符。Score为最多8位的正整数。 ?Name 查询玩家排名。该玩家的得分记录必定已经在前面上传。 ?Index 返回自第Index名开始的最多10名玩家名字。Index必定合法,即不小于1,也不大于当前有记录的玩家总数。输入文件总大小不超过2M。 NOTE:用C++的fstream读大规模数据的效率较低
Output
对于每条查询请求,输出相应结果。对于?Name格式的请求,应输出一个整数表示该玩家当前的排名。对于?Index格式的请求,应在一行中依次输出从第Index名开始的最多10名玩家姓名,用一个空格分隔。
Sample Input
+ADAM 1000000 加入ADAM的得分记录
+BOB 1000000 加入BOB的得分记录
+TOM 2000000 加入TOM的得分记录
+CATHY 10000000 加入CATHY的得分记录
?TOM 输出TOM目前排名
?1 目前有记录的玩家总数为4,因此应输出第1名到第4名。
+DAM 100000 加入DAM的得分记录
+BOB 1200000 更新BOB的得分记录
+ADAM 900000 更新ADAM的得分记录(即使比原来的差)
+FRANK 12340000 加入FRANK的得分记录
+LEO 9000000 加入LEO的得分记录
+KAINE 9000000 加入KAINE的得分记录
+GRACE 8000000 加入GRACE的得分记录
+WALT 9000000 加入WALT的得分记录
+SANDY 8000000 加入SANDY的得分记录
+MICK 9000000 加入MICK的得分记录
+JACK 7320000 加入JACK的得分记录
?2 目前有记录的玩家总数为12,因此应输出第2名到第11名。
?5 输出第5名到第13名。
?KAINE 输出KAINE的排名
Sample Output
CATHY TOM ADAM BOB
CATHY LEO KAINE WALT MICK GRACE SANDY JACK TOM BOB
WALT MICK GRACE SANDY JACK TOM BOB ADAM DAM
【分析】我就不吐槽HN抄袭浙江的题目了。。这道题的坑爹之处是字符串的处理。如何判断一个字符串是否出现?哈希本来是个好选择,但是我生怕25万的数据会被卡,只好硬生生地开了一棵字母树。。
为了巩固splay,全是手码的,于是乎调了半天,囧rz。说一下如何查询。他要找排名从K开始的10个人,我就设L=K,R=K+9。那么我们只需在正常的查询(可参见之前的程序)里把一个固定的值改成一个范围~~
还有,开始我一直T,后来一气之下,在询问时也找了个点splay一下,然后瞬秒出解。没事多转转~~
【AC代码】
/************************************************************** Problem: 1862 User: jiangshibiao Language: C++ Result: Accepted Time:1148 ms Memory:50828 kb ****************************************************************/ #include<cstdio> #include<cstring> #define N 300005 using namespace std; int son[N][3],Num[N][3],f[N],a[N],b[N],trie[N][27],L[N],score[N],ans[N]; char Str[N][12],opt[12]; int k,i,num,add,now,flag,tree,root,node,left,right,tot,Q,l,n,j,PRE; void Rotate(int x,int w) { int y=f[x],z=f[y]; son[y][3-w]=son[x][w]; if (son[x][w]) f[son[x][w]]=y; f[x]=z; Num[y][3-w]=Num[x][w]; Num[x][w]+=Num[y][w]+1; if (z) if (son[z][1]==y) son[z][1]=x;else son[z][2]=x; f[y]=x;son[x][w]=y; } inline void splay(int x) { int y; while (f[x]) { y=f[x]; if (!f[y]) { if (x==son[y][1]) Rotate(x,2);else Rotate(x,1);continue; } if (y==son[f[y]][1]) { if (x==son[y][1]) Rotate(y,2),Rotate(x,2); else Rotate(x,1),Rotate(x,2); } else { if (x==son[y][2]) Rotate(y,1),Rotate(x,1); else Rotate(x,2),Rotate(x,1); } } root=x; } inline void insert(int x,int num,int add) { if (!x) { root=++node; a[node]=add; b[node]=num; return; } if (add<a[x]||add==a[x]&&num>b[x]) { if (!son[x][1]) son[x][1]=++node,a[node]=add,b[node]=num,f[node]=x; else insert(son[x][1],num,add); Num[x][1]++; } else { if (!son[x][2]) son[x][2]=++node,a[node]=add,b[node]=num,f[node]=x; else insert(son[x][2],num,add); Num[x][2]++; } } inline void del(int x,int num,int add) { if (!x) return; if (add==a[x]&&num==b[x]) { splay(x); int find=son[x][1],temp=son[x][2]; if (!find&&!temp){root=0;return;} if (!find) {root=son[x][2];f[son[x][2]]=0;return;} if (!temp) {root=son[x][1];f[son[x][1]]=0;return;} while (son[find][2]) find=son[find][2]; f[son[x][1]]=0;splay(find);Num[find][2]+=Num[temp][1]+Num[temp][2]+1; son[find][2]=temp; f[temp]=find;root=find; return; } if (add<a[x]||add==a[x]&&num>b[x]) del(son[x][1],num,add); else del(son[x][2],num,add); } int walk(int k,int p) { if (p==l) {tree=k;return L[k]?L[k]:L[k]=i;} if (trie[k][opt[p]-65]) return walk(trie[k][opt[p]-65],p+1); trie[k][opt[p]-65]=++tot;flag=0; return walk(tot,p+1); } int Kth(int x,int now) { if (!x) return 0; if (add==a[x]&&num==b[x]) { PRE=x; return now+1+Num[x][2]; } if (add<a[x]||add==a[x]&&num>b[x]) return Kth(son[x][1],now+Num[x][2]+1); return Kth(son[x][2],now); } void Ask(int x,int now) { if (!x) return; int temp=Num[x][2]+now; if (temp>=left) Ask(son[x][2],now); if (temp+1>=left&&temp+1<=right) { ans[++n]=b[x]; PRE=x; } if (temp+1<right) Ask(son[x][1],temp+1); } void P(int k) { for (int i=1;i<strlen(Str[k]);i++) putchar(Str[k][i]); } int main() { scanf("%d",&Q);tot=1; for (i=1;i<=Q;i++) { scanf("%s",opt); l=strlen(opt); if (opt[0]==‘+‘) { scanf("%d",&k); flag=1;now=walk(1,1); if (flag) { del(root,now,score[now]);L[tree]=now=i; insert(root,now,k);splay(node); } else insert(root,now,k),splay(node); memcpy(Str[i],opt,sizeof(opt)); score[now]=k; } else if (opt[0]==‘?‘&&opt[1]>=‘0‘&&opt[1]<=‘9‘) { k=0;n=0; for (j=1;j<l;j++) k=k*10+opt[j]-48; left=k;right=k+9; Ask(root,0); for (j=1;j<n;j++) P(ans[j]),printf(" "); P(ans[n]);printf("\n");splay(PRE); } else { num=walk(1,1);add=score[num]; printf("%d\n",Kth(root,0));splay(PRE); } } return 0; }
【BZOJ1503】
1503: [NOI2004]郁闷的出纳员
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 5542 Solved: 1968
Description
OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?
Input
Output
输出文件的行数为F命令的条数加一。对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,如果k大于目前员工的数目,则输出-1。输出文件的最后一行包含一个整数,为离开公司的员工的总数。
Sample Input
I 60
I 70
S 50
F 2
I 30
S 15
A 5
F 1
F 2
Sample Output
20
-1
2
HINT
I命令的条数不超过100000 A命令和S命令的总条数不超过100 F命令的条数不超过100000 每次工资调整的调整量不超过1000 新员工的工资不超过100000
【分析】这道题说起来也是辛酸的历史。首先谈谈如何处理增加和减少工资的问题。我傻叉地开了一个f数组。每次判断一个splay中的结点时,他的实际权值是a[x]+f[q]-f[b[x]-1]。其中a[x]是刚进来的工资,q是现在这个询问,b[x]是x刚进来的时候所在的询问。(有点类似于前缀和的思想)后来网上看了一下,可以直接开一个数组记录~~~
再说明一下如何删除。(真是爽!)假设要删除<K的所有结点,我就先插入一个k-1的权值,然后把它旋转到根节点。现在,把根节点左边的东东全都咔嚓掉。做完后别忘了删除。
【代码】
/************************************************************** Problem: 1503 User: jiangshibiao Language: C++ Result: Accepted Time:1372 ms Memory:16040 kb ****************************************************************/ #include<cstdio> #define N 300005 using namespace std; int son[N][3],num[N][3],f[N],a[N],b[N],T[N]; char Str[N][12],opt[12],s[5]; int k,i,root,node,P,Min,q,Q,l,n,j,PRE,away; void Rotate(int x,int w) { int y=f[x],z=f[y]; son[y][3-w]=son[x][w]; if (son[x][w]) f[son[x][w]]=y; f[x]=z; num[y][3-w]=num[x][w]; num[x][w]+=num[y][w]+1; if (z) if (son[z][1]==y) son[z][1]=x;else son[z][2]=x; f[y]=x;son[x][w]=y; } void splay(int x) { while (f[x]) { int y=f[x],z=f[y]; if (!z) { if (son[y][1]==x) Rotate(x,2);else Rotate(x,1); continue; } if (son[z][1]==y) { if (son[y][1]==x) Rotate(y,2),Rotate(x,2); else Rotate(x,1),Rotate(x,2); } else { if (son[y][2]==x) Rotate(y,1),Rotate(x,1); else Rotate(x,2),Rotate(x,1); } } root=x; } void insert(int x) { if (!x){a[++node]=P;b[node]=q;return;} if (P<a[x]+T[q]-T[b[x]-1]) { if (son[x][1]) num[x][1]++,insert(son[x][1]); else son[x][1]=++node,a[node]=P,b[node]=q,f[node]=x; } else { if (son[x][2]) num[x][2]++,insert(son[x][2]); else son[x][2]=++node,a[node]=P,b[node]=q,f[node]=x; } } inline void del(int x) { int find=son[x][1],temp=son[x][2]; if (!find&&!temp){root=0;return;} if (!find) {root=son[x][2];f[son[x][2]]=0;return;} if (!temp) {root=son[x][1];f[son[x][1]]=0;return;} while (son[find][2]) find=son[find][2]; f[son[x][1]]=0;splay(find); num[find][2]+=num[root][2]; son[find][2]=temp;f[temp]=find;root=find; } int Ask(int x,int now) { int temp=num[x][2]+now; if (temp+1==P){PRE=x;return a[x]+T[q]-T[b[x]-1];} if (temp>=P) return Ask(son[x][2],now); else return Ask(son[x][1],temp+1); } int main() { scanf("%d%d",&Q,&Min); for (q=1;q<=Q;q++) { scanf("%s%d",s,&P);T[q]=T[q-1]; if (s[0]==‘I‘) { if (P<Min) continue; insert(root);splay(node); } if (s[0]==‘A‘) T[q]+=P; if (s[0]==‘S‘) { T[q]-=P;P=Min-1; insert(root);splay(node); son[root][1]=f[son[root][1]]=0; away+=num[root][1];num[root][1]=0; del(root); } if (s[0]==‘F‘) { if (num[root][1]+num[root][2]+1<P||!root) {printf("-1\n");continue;} printf("%d\n",Ask(root,0));splay(PRE); } } printf("%d",away); return 0; }