华师一附中OI组

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 2481|回复: 11
打印 上一主题 下一主题

P2596 [ZJOI2006]书架

[复制链接]

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
跳转到指定楼层
楼主
发表于 2020-4-7 19:15:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
https://www.luogu.com.cn/problem/P2596

题目描述
小T有一个很大的书柜。这个书柜的构造有些独特,即书柜里的书是从上至下堆放成一列。她用1到n的正整数给每本书都编了号。

小T在看书的时候,每次取出一本书,看完后放回书柜然后再拿下一本。由于这些书太有吸引力了,所以她看完后常常会忘记原来是放在书柜的什么位置。不过小T的记忆力是非常好的,所以每次放书的时候至少能够将那本书放在拿出来时的位置附近,比如说她拿的时候这本书上面有X本书,那么放回去时这本书上面就只可能有X-1、X或X+1本书。

当然也有特殊情况,比如在看书的时候突然电话响了或者有朋友来访。这时候粗心的小T会随手把书放在书柜里所有书的最上面或者最下面,然后转身离开。

久而久之,小T的书柜里的书的顺序就会越来越乱,找到特定的编号的书就变得越来越困难。于是她想请你帮她编写一个图书管理程序,处理她看书时的一些操作,以及回答她的两个提问:(1)编号为X的书在书柜的什么位置;(2)从上到下第i本书的编号是多少。

输入格式
第一行有两个数n,m,分别表示书的个数以及命令的条数;第二行为n个正整数:第i个数表示初始时从上至下第i个位置放置的书的编号;第三行到m+2行,每行一条命令。命令有5种形式:

1. Top S——表示把编号为S的书放在最上面。

2. Bottom S——表示把编号为S的书放在最下面。

3. Insert S T——T∈{-1,0,1},若编号为S的书上面有X本书,则这条命令表示把这本书放回去后它的上面有X+T本书;

4. Ask S——询问编号为S的书的上面目前有多少本书。

5. Query S——询问从上面数起的第S本书的编号。

输出格式
对于每一条Ask或Query语句你应该输出一行,一个数,代表询问的答案。

输入输出样例
输入 #1复制
10 10
1 3 2 7 5 8 10 4 9 6
Query 3
Top 5
Ask 6
Bottom 3
Ask 3
Top 6
Insert 4 -1
Query 5
Query 2
Ask 2
输出 #1复制
2
9
9
7
5
3
说明/提示
100%的数据,n,m <= 80000
回复

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
推荐
 楼主| 发表于 2020-4-13 20:43:25 | 只看该作者
加上两种查询,因为一般的修改都相对较麻烦,查询会比较简单,这两个都很容易:
Ask S——询问编号为S的书的上面目前有多少本书。把S对应的下标q[S],旋转到根后,输出它的左子树的sz
Query S——询问从上面数起的第S本书的编号。类似二分,设根的左子树的sz为l,右子树的sz为r的话,若l+1==s根为所求,s小的话去左边,s大的话去右边

  1. int Ask(int S)
  2. {
  3.         Splay(S,root);
  4.         int l=a[root].s[0];
  5.         return a[l].sz;
  6. }

  7. int Query(int rt, int S)
  8. {
  9.         int l=a[a[rt].s[0]].sz;  
  10.         if(l+1==S) return a[rt].val;
  11.         if(l+1>S) return Query(a[rt].s[0], S);
  12.         if(l+1<S) return Query(a[rt].s[1], S-(l+1));
  13. }
复制代码



继续用上面的数据,每个轮询的查,没有发现问题,
看看我是如何相互验证的。
  1.         for (int i=1; i<=n; i++)
  2.                 {
  3.                 int x=Ask(q[i]); ///看看i是上面有几本书
  4.                 cout<<x<<' ';
  5.                 int y=Query(root,x+1);
  6.                 cout<<y<<' '<<a[y].val<<endl;
  7.                  
  8.                 }
复制代码
回复 支持 1 反对 0

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
沙发
 楼主| 发表于 2020-4-7 19:19:53 | 只看该作者
读完题目的意思,显然可以使用BST来做。但是我喜欢先做一个粗暴的,试试树状数组记录前面如何?
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
板凳
 楼主| 发表于 2020-4-13 19:28:02 | 只看该作者
这是一个很基本很简单的SPLAY模板题,我以前见到过但是没有实际的写过,比他难得的多的都见过。周日晚上,我准备来写一个,同时记录下这个编程的过程:
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
地板
 楼主| 发表于 2020-4-13 19:31:26 | 只看该作者
有很多种方法可以做这个题,数据规模不大,8W,这里我选择使用SPLAY。秀一把。
1、周日晚上上完网课后9-00,准备开始做题,很久没有做这种思路不复杂,细节有很多要注意的地方的题目,估算了一下,可能需要2-3小时。
2、读懂了测试数据,准备步步为营来做
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
5#
 楼主| 发表于 2020-4-13 19:44:37 | 只看该作者
1、读懂题目之后做了一组1-20的二十个数的数据,准备把它每个数都查询一次,翻转一次,插入交换一次,求下位置,准备验算,因为这中题目联调会非常头疼,有必要在写的时候保证每个函数的正确性,否则一旦出错,调试将是可怕的。
2、因为是SPLAY,所以前面的ArrayPr,BSTPr()等函数还是写着,毕竟还不敢保证一写就能对。
3、初始状态不用SPLAY插入方式来做,因为旋转过多可能不好,采用类似线段树的递归建树模式。建成后打印时候打印输出,


  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=80100;
  4. struct node
  5. {
  6.         int val,fa,s[2],sz;  ///s0s1左右儿子
  7. } a[N];
  8. int p[N],n,m, tot, root;
  9. void ArrayPr()   /// 调试监视使用
  10. {
  11.         cout<<endl<<"  i ";
  12.         for (int i=1; i<=tot; i++) cout<<setw(3)<<i;
  13.         cout<<endl<<"val ";
  14.         for (int i=1; i<=tot; i++) cout<<setw(3)<<a[i].val;
  15.         cout<<endl<<" s0 ";
  16.         for (int i=1; i<=tot; i++) cout<<setw(3)<<a[i].s[0];
  17.         cout<<endl<<" s1 ";
  18.         for (int i=1; i<=tot; i++) cout<<setw(3)<<a[i].s[1];
  19.         cout<<endl<<" sz ";
  20.         for (int i=1; i<=tot; i++) cout<<setw(3)<<a[i].sz;
  21.         cout<<endl<<" fa ";
  22.         for (int i=1; i<=tot; i++) cout<<setw(3)<<a[i].fa;
  23.         cout<<endl;
  24. }
  25. void BSTPr(int rt)   ///中序输出以rt为根的BST
  26. {
  27.         if (a[rt].s[0]>0)  BSTPr(a[rt].s[0]);
  28.         cout<<a[rt].val<<' ';
  29.         if (a[rt].s[1]>0)  BSTPr(a[rt].s[1]);
  30. }
  31. int Build(int l,int r) ///前n个数直接建个满二叉树
  32. {
  33.         if (l>r) return 0;
  34.         if (l==r)
  35.                 {
  36.                         a[l].val=p[l];
  37.                         a[l].sz=1;
  38.                         return l;
  39.                 }
  40.         int m=(l+r)/2;
  41.         a[m].val=p[m];
  42.         int L=a[m].s[0]=Build(l,m-1);
  43.         int R=a[m].s[1]=Build(m+1,r);
  44.         a[L].fa=a[R].fa=m;
  45.         a[m].sz=a[L].sz+a[R].sz+1;
  46.         return m;
  47. }
  48. int main()
  49. {
  50.         cin>>n;
  51.         for (int i=1; i<=n; i++)cin>>p[i];
  52.         tot=n;
  53.         root=Build(1,n);


  54.         ///初始建树完毕
  55.         ArrayPr();
  56.         BSTPr(root);
  57.         
  58.         return 0;
  59. }
  60. /*
  61. 20
  62. 1 3 2 7 5 8 10 4 9 6
  63. 11 13 12 17 15 18 14 19 16 20
  64. */
复制代码
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
6#
 楼主| 发表于 2020-4-13 19:51:12 | 只看该作者
4、现在加入SPLAY部分,我的检查是把每个数字都查询一下,把他转到根,要是每个步骤都没错,估计旋转部分问题不大。

  1. void pushup(int rt)  
  2. {
  3.         int l=a[rt].s[0], r=a[rt].s[1];
  4.         a[rt].sz=a[l].sz+a[r].sz+1;
  5. }
  6. int Son(int x)
  7. {
  8.         int y=a[x].fa;
  9.         return  a[y].s[1]==x;
  10. }
  11. void R(int x)
  12. {
  13.         int y=a[x].fa;
  14.         int z=a[y].fa;
  15.         int k=Son(x);
  16.         int v=a[x].s[k^1];
  17.         a[y].s[k]=v,a[v].fa=y;
  18.         a[z].s[Son(y)]=x,a[x].fa=z;
  19.         a[x].s[k^1]=y,a[y].fa=x;
  20.         pushup(y),pushup(x);
  21. }
  22. void Splay(int x,int rt=root)
  23. {
  24.         rt=a[rt].fa;
  25.         int y,z;
  26.         while(a[x].fa!=rt)
  27.                 {
  28.                         y=a[x].fa,z=a[y].fa;
  29.                         if(z!=rt)
  30.                                 if(Son(x)==Son(y))R(y);
  31.                                 else R(x);
  32.                         R(x);
  33.                 }
  34.         if(rt==0)root=x;
  35. }
复制代码


主程序加上循环的测试段:
  1. for (int i=1; i<=n; i++)
  2.                 {
  3.                         cout<<endl<<i<<endl;
  4.                         Splay(i,root);
  5.                         ArrayPr();
  6.                         BSTPr(root);
  7.                 }
复制代码


测试了下,肉眼没有发现问题。最核心的部分完成了。
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
7#
 楼主| 发表于 2020-4-13 20:02:19 | 只看该作者
现在开始按题目要求一个个的解决,先看第一个操作,Top(S),把编号为S的书放在最上面,出问题了,哪本书的编号是S呢,总不能在val域中去查询吧?看来需要一个另外的数组,记录编号为S的书的起始位置,当然,这里就没有必要用什么map之类了,因为是一一对应的满射。加上数组q,q[i]表示编号为i的书对应的SPLAY的节点下标。
Top和Bottom的过程就是SPLAY的合并过程。以Top为例:
1、找到编号为S的点对应的节点x,把它旋转到根;
2、若x没有左子树,说明他已经是第一个,不用再处理;
3、设l为root的左子树,r为root的右子树,
4、在l子树中找最大的那个 p=l;while(a[p].s[0]>0) p=a[p].s[0];
5、把p旋到l的位置,这么来l就没有右子树了。
6、把r接到l的右儿子处。
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
8#
 楼主| 发表于 2020-4-13 20:05:27 | 只看该作者
写出了这段代码,然后测试,把那20个数从最大的开始,每个都往顶上挪一次,最后形成了一个递增的序列。

  1. void Top(int S)
  2. {
  3.         Splay(S,root);
  4.         int  x=root;
  5.         int l=a[x].s[0];
  6.         int r=a[x].s[1];///右儿子
  7.         if (r==0) a[x].s[1]=l;
  8.         else
  9.                 {
  10.                         int p=r;
  11.                         ///找最左节点
  12.                         while (a[p].s[0]>0) p=a[p].s[0];
  13.                         Splay(p,r);
  14.                         a[p].s[0]=l;
  15.                         a[l].fa=p;
  16.                         pushup(p);
  17.                 }
  18.         a[x].s[0]=0;
  19. }
复制代码


加上了q数组,主程序里测试这段代码:
  1.         for (int i=1; i<=n; i++)
  2.                 {
  3.                         int x=q[i];///编号i对应的splay中的数组下标
  4.                         Top(x);
  5.                         cout<<endl;
  6.                         BSTPr(root);
  7.                 }
复制代码



运行结果很美观:

1 3 2 7 5 8 10 4 9 6 11 13 12 17 15 18 14 19 16 20
2 1 3 7 5 8 10 4 9 6 11 13 12 17 15 18 14 19 16 20
3 2 1 7 5 8 10 4 9 6 11 13 12 17 15 18 14 19 16 20
4 3 2 1 7 5 8 10 9 6 11 13 12 17 15 18 14 19 16 20
5 4 3 2 1 7 8 10 9 6 11 13 12 17 15 18 14 19 16 20
6 5 4 3 2 1 7 8 10 9 11 13 12 17 15 18 14 19 16 20
7 6 5 4 3 2 1 8 10 9 11 13 12 17 15 18 14 19 16 20
8 7 6 5 4 3 2 1 10 9 11 13 12 17 15 18 14 19 16 20
9 8 7 6 5 4 3 2 1 10 11 13 12 17 15 18 14 19 16 20
10 9 8 7 6 5 4 3 2 1 11 13 12 17 15 18 14 19 16 20
11 10 9 8 7 6 5 4 3 2 1 13 12 17 15 18 14 19 16 20
12 11 10 9 8 7 6 5 4 3 2 1 13 17 15 18 14 19 16 20
13 12 11 10 9 8 7 6 5 4 3 2 1 17 15 18 14 19 16 20
14 13 12 11 10 9 8 7 6 5 4 3 2 1 17 15 18 19 16 20
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 17 18 19 16 20
16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 17 18 19 20
17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 18 19 20
18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 19 20
19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 20
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
9#
 楼主| 发表于 2020-4-13 20:13:45 | 只看该作者
稍加修改变成Bottom,继续测试一下 ,就不写了,但是我自己中间有一个l和r写错了,幸亏通过这种方法检查出来了。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|服务支持:DZ动力|华师一附中OI组  

GMT+8, 2024-11-2 06:30 , Processed in 0.200162 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表