[BZOJ4825][HNOI2017]单旋(线段树+Splay)
4825: [Hnoi2017]单旋
Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 667 Solved: 342
[Submit][Status][Discuss]Description
H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能。有一天,邪恶的“卡”带着他的邪恶的“常数”来企图毁灭 H 国。“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快。“卡”称“单旋 splay”为“spaly”。虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,spaly 马上成为他的信仰。 而 H 国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由 m 个操作构成,他知道这样的数据肯定打垮 spaly,但是国王还有很多很多其他的事情要做,所以统计每个操作所需要的实际代价的任务就交给你啦。数据中的操作分为五种:1. 插入操作:向当前非空 spaly 中插入一个关键码为 key 的新孤立节点。插入方法为,先让 key 和根比较,如果key 比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,key 比当前子树根 x 小,而 x 的左子树为空,那就让 key 成为 x 的左孩子; 或者 key 比当前子树根 x 大,而 x 的右子树为空,那就让 key 成为x 的右孩子。该操作的代价为:插入后,key 的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树。(各节点关键码互不相等。对于“深度”的解释见末尾对 spaly 的描述)。2. 单旋最小值:将 spaly 中关键码最小的元素 xmin 单旋到根。操作代价为:单旋前 xmin 的深度。(对于单旋操作的解释见末尾对 spaly 的描述)。3. 单旋最大值:将 spaly 中关键码最大的元素 xmax 单旋到根。操作代价为:单旋前 xmax 的深度。4. 单旋删除最小值:先执行 2 号操作,然后把根删除。由于 2 号操作之后,根没有左子树,所以直接切断根和右子树的联系即可(具体见样例解释)。 操作代价同 2 号操 作。5. 单旋删除最大值:先执行 3 号操作,然后把根删除。 操作代价同 3 号操作。对于不是 H 国的人,你可能需要了解一些 spaly 的知识,才能完成国王的任务:a. spaly 是一棵二叉树,满足对于任意一个节点 x,它如果有左孩子 lx,那么 lx 的关键码小于 x 的关键码。如果有右孩子 rx,那么 rx 的关键码大于 x 的关键码。b. 一个节点在 spaly 的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。c. 单旋操作是对于一棵树上的节点 x 来说的。一开始,设 f 为 x 在树上的父亲。如果 x 为 f 的左孩子,那么执行 zig(x) 操作(如上图中,左边的树经过 zig(x) 变为了右边的树),否则执行 zag(x) 操作(在上图中,将右边的树经过 zag(f) 就变成了左边的树)。每当执 行一次 zig(x) 或者 zag(x),x 的深度减小 1,如此反复,直到 x 为根。总之,单旋 x 就是通过反复执行 zig 和 zag 将 x 变为根。Input
第一行单独一个正整数 m。接下来 m 行,每行描述一个操作:首先是一个操作编号 c∈[1,5],即问题描述中给出的五种操作中的编号,若 c= 1,则再输入一个非负整数 key,表示新插入节点的关键码。1≤m≤10^5,1≤key≤10^9所有出现的关键码互不相同。任何一个非插入操作,一定保证树非空。在未执行任何操作之前,树为空Output
输出共 m 行,每行一个整数,第 i 行对应第 i 个输入的操作的代价。Sample Input
5
1 2
1 1
1 3
4
5Sample Output
1
2
2
2
2HINT
Source
代码用时:2h
感觉代码难度很低啊为什么我还是花了很长时间。。
思维比较简单,只要把三种操作想清楚了就差不多了。
比较自然的想法应该是Splay,但线段树实现复杂度和常数都更优。
线段树做法网上好像只找到一个(而且没有标记永久化的代码并不是很优美常数也稍大)
这里有一个自认为比较优的做法。
(本题线段树做法的思想还是有点巧妙的,利用了题目只单旋最值的设定)
12140KB,1108ms
- #include<set>
- #include<cstdio>
- #include<algorithm>
- #include<iostream>
- #define rep(i,l,r) for (int i=l; i<=r; i++)
- using namespace std;
- template<typename T>inline void rd(T &x){
- int t; char ch;
- for (t=; !isdigit(ch=getchar()); t=(ch=='-'));
- for (x=ch-''; isdigit(ch=getchar()); x=x*+ch-'');
- if (t) x=-x;
- }
- const int N=;
- int m,tot,d,rt,D[N<<],rs[N],ls[N],fa[N],a[N],b[N],op[N][];
- set<int>S;
- set<int>::iterator it,sub,pro;
- void mdf(int x,int L,int R,int l,int r,int k){
- if (L==l && R==r){ D[x]+=k; return; }
- int mid=(L+R)>>;
- if (r<=mid) mdf(x<<,L,mid,l,r,k);
- else if (l>mid) mdf((x<<)|,mid+,R,l,r,k);
- else mdf(x<<,L,mid,l,mid,k),mdf((x<<)|,mid+,R,mid+,r,k);
- }
- int que(int x,int L,int R,int k){
- if (L==R) return D[x];
- int mid=(L+R)>>;
- if (k<=mid) return D[x]+que(x<<,L,mid,k);
- else return D[x]+que((x<<)|,mid+,R,k);
- }
- int main(){
- freopen("bzoj4825.in","r",stdin);
- freopen("bzoj4825.out","w",stdout);
- rd(m);
- rep(i,,m){
- rd(op[i][]); if (op[i][]==) rd(op[i][]);
- if (op[i][]==) b[++tot]=op[i][];
- }
- sort(b+,b+tot+); tot=unique(b+,b+tot+)-b-;
- rep(i,,m) if (op[i][]==) a[i]=lower_bound(b+,b+tot+,op[i][])-b;
- rep(i,,m){
- if (op[i][]==){
- if (S.empty()) rt=a[i],d=;
- else{
- it=S.upper_bound(a[i]),sub=it;
- if (sub==S.begin()) ls[*sub]=a[i],fa[a[i]]=*sub,d=que(,,tot,*sub)+;
- else{
- pro=--it;
- if (sub==S.end()) rs[*pro]=a[i],fa[a[i]]=*pro,d=que(,,tot,*pro)+;
- else if (que(,,tot,*sub)<que(,,tot,*pro))
- rs[*pro]=a[i],fa[a[i]]=*pro,d=que(,,tot,*pro)+;
- else ls[*sub]=a[i],fa[a[i]]=*sub,d=que(,,tot,*sub)+;
- }
- }
- printf("%d\n",d); mdf(,,tot,a[i],a[i],d-que(,,tot,a[i]));
- S.insert(a[i]);
- }
- if (op[i][]== || op[i][]==){
- it=S.begin(); d=que(,,tot,*it); printf("%d\n",d);
- if ((*it)!=rt){
- mdf(,,tot,,tot,); mdf(,,tot,*it,*it,-d);
- int t=fa[*it]; ls[t]=fa[*it]=;
- if (rs[*it]){
- mdf(,,tot,(*it)+,t-,-);
- fa[rs[*it]]=t; ls[t]=rs[*it];
- }
- rs[*it]=rt; fa[rt]=*it; rt=*it;
- }
- if (op[i][]==) rt=rs[*it],rs[*it]=fa[rt]=,mdf(,,tot,,tot,-),S.erase(*it);
- }
- if (op[i][]== || op[i][]==){
- it=S.end(); it--; d=que(,,tot,*it); printf("%d\n",d);
- if ((*it)!=rt){
- mdf(,,tot,,tot,); mdf(,,tot,*it,*it,-d);
- int t=fa[*it]; rs[t]=fa[*it]=;
- if (ls[*it]){
- mdf(,,tot,t+,(*it)-,-);
- fa[ls[*it]]=t; rs[t]=ls[*it];
- }
- ls[*it]=rt; fa[rt]=*it; rt=*it;
- }
- if (op[i][]==) rt=ls[*it],ls[*it]=fa[rt]=,mdf(,,tot,,tot,-),S.erase(*it);
- }
- }
- return ;
- }
惊了,splay跑的比线段树还快。
5752KB,900ms
代码用时:1h
一开始想自己写出来,思路并不复杂但代码很繁琐(模板占了大部分但想敲对并不容易)。
看了网上的代码抄了一份下来竟然1A,可能是我抄代码的经历中最顺利的一次吧。
但抄来的代码终究不是自己的,要勇于写这种代码稍长的题目,后面还会有比这长的多的题!
- #include<cstdio>
- #include<iostream>
- #include<algorithm>
- #define ls c[x][0]
- #define rs c[x][1]
- #define rep(i,l,r) for (int i=l; i<=r; i++)
- using namespace std;
- template<typename T>inline void rd(T &x){
- int t; char ch;
- for (t=; !isdigit(ch=getchar()); t=(ch=='-'));
- for (x=ch-''; isdigit(ch=getchar()); x=x*+ch-'');
- if (t) x=-x;
- }
- const int inf=,N=;
- int n,cnt,rt,Q,fa[N],v[N],sz[N],c[N][],s[N],dep[N],mn[N];
- void push(int x){
- v[ls]+=v[x]; dep[ls]+=v[x]; mn[ls]+=v[x];
- v[rs]+=v[x]; dep[rs]+=v[x]; mn[rs]+=v[x];
- v[x]=;
- }
- void upd(int x){
- sz[x]=sz[ls]+sz[rs]+; mn[x]=dep[x];
- if (ls) mn[x]=min(mn[x],mn[ls]);
- if (rs) mn[x]=min(mn[x],mn[rs]);
- }
- void rot(int &rt,int x){
- int y=fa[x],z=fa[y],w=(c[y][]==x);
- if (y==rt) rt=x; else c[z][c[z][]==y]=x;
- fa[x]=z; fa[y]=x; fa[c[x][w^]]=y;
- c[y][w]=c[x][w^]; c[x][w^]=y; upd(y);
- }
- void splay(int &rt,int x){
- while (x!=rt) {
- int y=fa[x],z=fa[y];
- if (y!=rt){
- if ((c[z][]==y)^(c[y][]==x)) rot(rt,x); else rot(rt,y);
- }
- rot(rt,x);
- }
- upd(x);
- }
- void ins(int &x,int S,int d,int lst){
- if (!x){
- x=++cnt, s[cnt]=S; dep[cnt]=mn[cnt]=d;
- sz[cnt]=; fa[cnt]=lst; return;
- }
- ins(c[x][S>s[x]],S,d,x); upd(x);
- }
- int getpre(int x,int S){
- if (!x) return ;
- if (v[x]) push(x);
- if (s[x]>S) return getpre(c[x][],S);
- Q=getpre(c[x][],S);
- if (Q) return Q; else return x;
- }
- int getnxt(int x,int S){
- if (!x) return ;
- if (v[x]) push(x);
- if (s[x]<S) return getnxt(rs,S);
- Q=getnxt(ls,S);
- if (Q) return Q; else return x;
- }
- int find(int x,int k){
- if (v[x]) push(x);
- if (sz[ls]+==k) return x;
- if (sz[ls]+<k) return find(rs,k-sz[ls]-);
- return find(ls,k);
- }
- int getl(int x,int d){
- if (!x) return ;
- if (v[x]) push(x);
- if (min(mn[ls],dep[x])>=d) return getl(rs,d)+sz[ls]+;
- else return getl(ls,d);
- }
- int getr(int x,int d){
- if (!x) return ;
- if (v[x]) push(x);
- if (min(mn[rs],dep[x])>=d) return getr(ls,d)+sz[rs]+;
- else return getr(rs,d);
- }
- int split(int l,int r){
- int t1=find(rt,l-),t2=find(rt,r+);
- splay(rt,t1); splay(c[rt][],t2); return c[c[rt][]][];
- }
- void mdf(int l,int r,int ad){
- int y=split(l,r); v[y]+=ad; mn[y]+=ad; dep[y]+=ad;
- }
- void change(int x,int S){
- if (v[x]) push(x);
- if (s[x]==S) dep[x]=; else change(c[x][S>s[x]],S);
- upd(x);
- }
- int main(){
- freopen("bzoj4825.in","r",stdin);
- freopen("bzoj4825.out","w",stdout);
- rd(n); ins(rt,-inf,inf,); ins(rt,inf,inf,); mn[]=inf;
- rep(i,,n){
- int op,x; rd(op);
- if (op==){
- rd(x); int t1=getpre(rt,x),t2=getnxt(rt,x);
- int D=max(t1> ? dep[t1] : ,t2> ? dep[t2] : )+;
- ins(rt,x,D,); splay(rt,cnt); printf("%d\n",D);
- }
- if (!(op&)){
- int x=find(rt,),y=min(getl(rt,dep[x]),sz[rt]-)-;
- printf("%d\n",dep[x]); mdf(,sz[rt]-,);
- if (y>) mdf(,y+,-);
- change(rt,s[x]);
- }
- if ((op&)&&(op>)){
- int x=find(rt,sz[rt]-),y=min(getr(rt,dep[x]),sz[rt]-)-;
- printf("%d\n",dep[x]); mdf(,sz[rt]-,);
- if (y>) mdf(sz[rt]-y,sz[rt]-,-);
- change(rt,s[x]);
- }
- if (op>=){
- if (op==) splay(rt,find(rt,));
- else splay(rt,find(rt,sz[rt]-));
- int l=(op==),r=l^,y=c[rt][l];
- c[y][r]=c[rt][r]; fa[y]=; fa[c[rt][r]]=y; rt=y;
- v[rt]-=; upd(rt);
- }
- }
- return ;
- }
[BZOJ4825][HNOI2017]单旋(线段树+Splay)的更多相关文章
- 【BZOJ4825】[Hnoi2017]单旋 线段树+set
[BZOJ4825][Hnoi2017]单旋 Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能 ...
- 【bzoj4825】[Hnoi2017]单旋 线段树+STL-set
题目描述 H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能.有一天 ...
- BZOJ.4825.[AHOI/HNOI2017]单旋(线段树)
BZOJ LOJ 洛谷 这题不难啊,我怎么就那么傻,拿随便一个节点去模拟.. 我们只需要能够维护,将最小值或最大值转到根.模拟一下发现,对于最小值,它的右子树深度不变(如果存在),其余节点深度全部\( ...
- 洛谷P3721 [AH2017/HNOI2017]单旋(线段树 set spaly)
题意 题目链接 Sol 这题好毒瘤啊.. 首先要观察到几个性质: 将最小值旋转到根相当于把右子树变为祖先的左子树,然后将原来的根变为当前最小值 上述操作对深度的影响相当于右子树不变,其他的位置-1 然 ...
- BZOJ4825 [Hnoi2017]单旋 【线段树】
题目链接 BZOJ4825 题解 手模一下操作,会发现一些很优美的性质: 每次旋到根,只有其子树深度不变,剩余点深度\(+1\) 每次旋到根,[最小值为例]右儿子接到其父亲的左儿子,其余点形态不改变, ...
- BZOJ4825: [Hnoi2017]单旋(Splay)
题面 传送门 题解 调了好几个小时--指针太难写了-- 因为只单旋最值,我们以单旋\(\min\)为例,那么\(\min\)是没有左子树的,而它旋到根之后,它的深度变为\(1\),它的右子树里所有节点 ...
- bzoj4825 [Hnoi2017]单旋
Description H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构.伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必 ...
- [BZOJ4825][HNOI2017]单旋spaly
BZOJ Luogu 题目太长了,就不放了. 题解 首先声明一点,无论是splay还是spaly,插入一个新的元素,都要rotate到根!所以说题目也算是给了一个错误示范吧. 我们发现把最值旋转到根并 ...
- 4825: [Hnoi2017]单旋
4825: [Hnoi2017]单旋 链接 分析: 以后采取更保险的方式写代码!!!81行本来以为不特判也可以,然后就总是比答案大1,甚至出现负数,调啊调啊调啊调~~~ 只会旋转最大值和最小值,以最小 ...
随机推荐
- Broken Necklace
Description 你有一条由N个红色的,白色的,或蓝色的珠子组成的项链(3<=N<=350),珠子是随意安排的. 这里是 n=29 的二个 例子: 1 2 1 2 r b b r b ...
- Windows Live Writer博客草稿迁移的一种解决方案
作为一个苦逼的码农,喜欢写博客做总结是很正常的事,写博客写的久的人都接触过各种客户端工具,最流行的就是Windows Live Writer了. 作为一个苦逼的码农,换电脑也是很经常的事,经常会出现一 ...
- CRF原理解读
概率有向图又称为贝叶斯网络,概率无向图又称为马尔科夫网络.具体地,他们的核心差异表现在如何求 ,即怎么表示 这个的联合概率. 概率图模型的优点: 提供了一个简单的方式将概率模型的结构可视化. 通过 ...
- Coursera在线学习---第二节.Octave学习
1)两个矩阵相乘 A*B 2)两个矩阵元素位相乘(A.B矩阵中对应位置的元素相乘) A.*B 3)矩阵A的元素进行平方 A.^2 4)向量或矩阵中的元素求倒数 1./V 或 1./A 5) ...
- perl6正则 6: 大小写/空白/匹配所有符合
这个 :g 只能写在外面 m:g /re/
- 5-python的封装与结构 - set集合
目录 1 封装与解构 1.1 封装 1.2 解构 1.3 Python3的解构 2 set类型 2.1 set的定义 2.2 set的基本操作 2.2.1 增加元素 2.2.2 删除元素 2.2.3 ...
- sicily 1240. Faulty Odometer
Description You are given a car odometer which displays the miles traveled as an integer. The odomet ...
- windows安装React Native开发运行环境
React Native是什么 React Native是facebook开源的一个用于开发app的框架.React Native的设计理念:既拥有Native (原生) 的用户体验.又保留React ...
- u-boot界面添加命令[demo]
目标板:2440 如何在u-boot界面中增加命令 在/common/目录下建立文件,调用执行函数do_bootm就行,然后在修改Makefile,就OK了. 比如在u-boot界面添加命令test ...
- Linux /etc/cron.d作用(转自 定时任务crontab cron.d)
原文链接:http://huangfuligang.blog.51cto.com/9181639/1608549 一.cron.d增加定时任务 当我们要增加全局性的计划任务时,一种方式是直接修改/et ...