HDU 4453 Looploop (2012年杭州赛区现场赛A题)
2015-08-31 15:01
399 查看
1.题目描述:点击打开链接
2.解题思路:本题是伸展树的基本题型,不过由于是第一次使用这种数据结构,先补了一下BST和Treap的基础知识,然后才开始学这种数据结构。不难发现,伸展树最基本且最核心的操作就是伸展操作,它能够将一个指定的结点通过旋转操作逐步转为树根。同时由于伸展树本质上也是一种BST,因此同时具备BST的基本功能,比如查询操作。我们每次都把箭头指向的位置作为树根。
首先是建树过程,我们从区间的中间位置开始,把它当做树根,如果m>L,那么递归建立左子树,如果m<R,那么递归建立右子树。这里有一个不太容易理解的地方是,为什么Splay操作不会影响我们查找原来数组中第k个位置的数是谁?因为如果把伸展树看做BST,那么这里的键值是子树的结点数。这样,只要一开始建树是从中间开始建立的,那么结点数的关系就不会改变了:数组下标为i的左子树永远都是i-1个结点,右子树永远都是n-i个结点。而Splay操作也是严格根据BST键值关系来旋转的,所以不会改变结点数目的关系。这样,不管伸展树变成了什么样子,我们都能快速找到原来数组下标为i的那个结点。由此还可以得到如下的推论:执行完Splay(root,1)后,这棵树只有右子树,左子树为空;执行完Splay(root,n)后,这棵树只有左子树,右子树为空。
下面来谈一谈如何利用伸展树来完成题目中给定的6种操作。
1.add X:要求把从箭头指向的位置到右边第k2个位置的所有数都增加X。显然,我们需要一个lazy标记来完成这一操作,不妨设为add。那么首先把k2位置伸展为树根,这样左子树的所有结点和根就是我们要进行add操作的所有结点。对根的value+add,并将左子树的lazy标记+add即可。
2.reverse:要求把从箭头指向的位置到右边第k1个位置的数反转。首先执行Splay(root,k1),并把它的右子树暂时分开,标记root->flip=1,翻转完毕后再将右子树合并回去。
3.insert X:要求在箭头指向的位置的下一个位置插入X。首先执行Splay(root,1),这样,只需要在root和root->ch[1]之间插入X即可,同时n++。
4.delete:要求删除箭头指向的位置,并把箭头向右移动一位。首先执行Splay(root,1),这样,所有结点都在右子树上,只需要把root设置为右子树的根即可,同时n--。
5.move X:当X==1时,将箭头向左移动一位;当X==2时,将箭头向右移动一位。当X==1时,先执行Splay(root,n),此时所有结点都在左子树上(注意此时a
是树根,a[1]已经在左子树上了),把根和左子树分离,同时对左子树执行Splay(tmp,1)(tmp即左子树树根)。然后让左子树成为根的右子树即可,即root->ch[1]=tmp。用同样的方法可以处理右子树。
6.query:输出箭头指向的数字。直接输出root->value即可。
由此,我们成功利用伸展树实现了上述的6种操作。
3.代码:
2.解题思路:本题是伸展树的基本题型,不过由于是第一次使用这种数据结构,先补了一下BST和Treap的基础知识,然后才开始学这种数据结构。不难发现,伸展树最基本且最核心的操作就是伸展操作,它能够将一个指定的结点通过旋转操作逐步转为树根。同时由于伸展树本质上也是一种BST,因此同时具备BST的基本功能,比如查询操作。我们每次都把箭头指向的位置作为树根。
首先是建树过程,我们从区间的中间位置开始,把它当做树根,如果m>L,那么递归建立左子树,如果m<R,那么递归建立右子树。这里有一个不太容易理解的地方是,为什么Splay操作不会影响我们查找原来数组中第k个位置的数是谁?因为如果把伸展树看做BST,那么这里的键值是子树的结点数。这样,只要一开始建树是从中间开始建立的,那么结点数的关系就不会改变了:数组下标为i的左子树永远都是i-1个结点,右子树永远都是n-i个结点。而Splay操作也是严格根据BST键值关系来旋转的,所以不会改变结点数目的关系。这样,不管伸展树变成了什么样子,我们都能快速找到原来数组下标为i的那个结点。由此还可以得到如下的推论:执行完Splay(root,1)后,这棵树只有右子树,左子树为空;执行完Splay(root,n)后,这棵树只有左子树,右子树为空。
下面来谈一谈如何利用伸展树来完成题目中给定的6种操作。
1.add X:要求把从箭头指向的位置到右边第k2个位置的所有数都增加X。显然,我们需要一个lazy标记来完成这一操作,不妨设为add。那么首先把k2位置伸展为树根,这样左子树的所有结点和根就是我们要进行add操作的所有结点。对根的value+add,并将左子树的lazy标记+add即可。
2.reverse:要求把从箭头指向的位置到右边第k1个位置的数反转。首先执行Splay(root,k1),并把它的右子树暂时分开,标记root->flip=1,翻转完毕后再将右子树合并回去。
3.insert X:要求在箭头指向的位置的下一个位置插入X。首先执行Splay(root,1),这样,只需要在root和root->ch[1]之间插入X即可,同时n++。
4.delete:要求删除箭头指向的位置,并把箭头向右移动一位。首先执行Splay(root,1),这样,所有结点都在右子树上,只需要把root设置为右子树的根即可,同时n--。
5.move X:当X==1时,将箭头向左移动一位;当X==2时,将箭头向右移动一位。当X==1时,先执行Splay(root,n),此时所有结点都在左子树上(注意此时a
是树根,a[1]已经在左子树上了),把根和左子树分离,同时对左子树执行Splay(tmp,1)(tmp即左子树树根)。然后让左子树成为根的右子树即可,即root->ch[1]=tmp。用同样的方法可以处理右子树。
6.query:输出箭头指向的数字。直接输出root->value即可。
由此,我们成功利用伸展树实现了上述的6种操作。
3.代码:
#include<iostream> #include<algorithm> #include<cassert> #include<string> #include<sstream> #include<set> #include<bitset> #include<vector> #include<stack> #include<map> #include<queue> #include<deque> #include<cstdlib> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<cctype> #include<functional> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define me(s) memset(s,0,sizeof(s)) #define rep(i,n) for(int i=0;i<(n);i++) typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; typedef pair <int, int> P; const int N=200000+5; int a ; int n,m,k1,k2; struct Node { Node*ch[2]; int size; int value; int flip,add; Node(); Node(int); void pushup() { size=ch[0]->size+ch[1]->size+1; } void pushdown() { if(flip) { swap(ch[0],ch[1]); ch[0]->flip^=1; ch[1]->flip^=1; flip=0; } if(add) { value+=add; ch[0]->add+=add; ch[1]->add+=add; add=0; } } int rank() { return ch[0]->size+1; } int cmp(int k) { if(k==rank())return -1; return k<rank()?0:1; } }null,*root,lk ; int cnt; Node*newnode(int v) { lk[cnt]=Node(v); return lk+cnt++; } Node::Node() { ch[0]=ch[1]=&null; size=add=flip=0; } Node::Node(int v):value(v) { ch[0]=ch[1]=&null; size=1; flip=add=0; } void rotate(Node*&o,int d) { o->pushdown(); Node*k=o->ch[d^1]; o->ch[d^1]=k->ch[d]; k->ch[d]=o; o->pushup(); k->pushup(); o=k; } void splay(Node*&o,int k) { o->pushdown(); int d=o->cmp(k); if(~d) { if(d)splay(o->ch[d],k-o->rank()); else splay(o->ch[d],k); rotate(o,d^1); } } void build(Node*&o,int l,int r)//建树 { int m=(l+r)>>1; o=newnode(a[m]); if(m>l)build(o->ch[0],l,m-1); if(m<r)build(o->ch[1],m+1,r); o->pushup(); } void add(int x)//增加操作 { splay(root ,k2); root->value+=x; root->ch[0]->add+=x; } void reverse()//翻转操作 { splay(root ,k1); Node*tmp=root->ch[1]; root->ch[1]=&null; root->flip=1; splay(root,k1); root->ch[1]=tmp; root->pushup(); } void insert(int x)//插入操作 { splay(root,1); Node*tmp=newnode(x); tmp->ch[1]=root->ch[1]; tmp->pushup(); root->ch[1]=tmp; root->pushup(); n++; } void remove()//删除操作 { splay(root,1); root=root->ch[1]; n--; } void move(int x)//移动操作 { if(x==1) { splay(root,n); Node*tmp=root->ch[0]; root->ch[0]=&null; //分离左子树,此时的根已经是a 了 splay(tmp,1); //执行后,a[1]成为了左子树的树根 root->ch[1]=tmp; //把左子树当做根的右子树 root->pushup(); //更新size } else { splay(root,1); Node*tmp=root->ch[1]; root->ch[1]=&null; splay(tmp,n-1); //注意;不要写成了splay(tmp,2),因为将1旋转为根后,左子树的树根的size=n,应该查找n-1的位置 root->ch[0]=tmp; root->pushup(); } } int main() { int kase=1; while(~scanf("%d%d%d%d",&n,&m,&k1,&k2)&&(n||m||k1||k2)) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); cnt=0; build(root,1,n); printf("Case #%d:\n",kase++); while(m--) { char cmd[10]; scanf("%s",cmd); if(cmd[0]=='a') { int x;scanf("%d",&x); add(x); } if(cmd[0]=='r')reverse(); if(cmd[0]=='i') { int x;scanf("%d",&x); insert(x); } if(cmd[0]=='d')remove(); if(cmd[0]=='m') { int x;scanf("%d",&x); move(x); } if(cmd[0]=='q') { splay(root,1); printf("%d\n",root->value); } } } }
相关文章推荐
- CentOS搭建socket5代理服务器
- linux概念和体系
- Linux 高可用(HA)集群基本概念详解二
- ssl 在nginx上的部署示例
- Linux 高可用(HA)集群基本概念详解一
- linux 学习之命令总结(三)
- 配置文件的使用 Properties
- Tomcat架构详解(三) Request和Response处理的全过程
- ShellExecuteEx调用第三方程序
- docker
- linux如何查看文件内容
- O 4000 C之@property和@synthesize
- 查看linux系统版本命令
- linux设备驱动编写_tasklet机制(转)
- iisexpress局域网内调试网站
- windows下拷贝文件到linux下
- linux下sed的使用
- 新手推荐:Hadoop安装教程_单机/伪分布式配置_Hadoop-2.7.1/Ubuntu14.04
- 新手推荐:Hadoop安装教程_单机/伪分布式配置_Hadoop-2.7.1/Ubuntu14.04
- Tomcat配置忽略应用名称的解决方案