一点题外话:
这个题忒折磨人了,我写了将近一天,两顿饭没吃。直到最后我下决心晚饭不吃我也要写出来才搞出来。写完后发现其实也不是很难,245行程序就搞定了。Splay真的不错,反正我是懒得学块状链表了。

————————————以下是题解——————————
这是NOI有史以来出过的最BT的数据结构题了,不少人写的都是块状链表,我来个Splay的(比起编程难度,其实朴素才是王道)。
建立一棵Splay,每个节点上要维护一下几个信息:数值value,子树大小size,子树和sum,子数内和最大的子数列 maxsum,子树区间内由左边界能够延伸的和最大子数列mls,子树区间内由右边界能够延伸的和最大子数列mrs,以及两个标记:子树反转标记rev,子树同化标记same。为了避免判断边界条件,首先插入两个节点,作为开头和结尾,其永久排名分别为1和当前数列长度。
插入和删除操作和NOI2003的文本编辑器类似。插入一段数列,把第pos+1个元素splay到根节点,,把第pos+2个元素 splay到根节点的右子树根节点,把要插入的这段数列建成一条链最好,然后把这个新建的链接到根节点右子树的左子树上,最后把链的尾splay到根节点。自底向上splay过程中要维护各项信息。
删除就是把第pos个元素splay到根节点,把第pos+tot+1个元素splay到根节点的右子树根节点,然后把根节点右子树的左子树直接砍掉就行了。由于数据规模比较大,如果内存比较紧张,要考虑回收空间。
考虑到修改和翻转的区间可能很大,暴力的肯定不行。取得区间的方法和删除一样,然后我们就用标记标识代表这个区间的这棵子树,今后访问到这这棵子树时,处理标记然后标记下传。具体来说,下传rev标记时,交换两个子树以及mls和mrs的值,然后两棵子树获得标记。下传same标记时,先把当前节点值传给子节点,维护节点的sum值为当前节点的value * size。然后如果当前节点值为非负数,令maxsum,mls,mrs的值为value * size,如果当前节点值为负数,令maxsum,mls,mrs的值为p->value。要注意的是,任何时候只要访问到带标记的节点,一定要标记下传,尤其不要忘了在splay旋转的时候。
另一个比较复杂的地方为自底向上维护节点信息,我们有size,sum,mls,mrs,maxsum五个信息需要同时维护。其中size,sum比较简单。
mls的取值有三种可能,直接继承与左子树的mls值,整个左子树的sum + 当前节点value,以及左子树的sum + 当前节点value + 右子树的mls,取三者最大值。mrs类似于mls。
maxsum的维护更为复杂,可能直接为当前节点value,继承左子树的maxsum,继承右子树的maxsum,左子树的mrs + 当前节点value,右子树的mls + 当前节点value,以及左子树的mrs + 右子树的mls + 当前节点value,这五种情况,同样取最大值。
有了上述的维护,求一段区间和也就不难了。同样的方法找到待求和区间,直接读取子树根节点的sum值即可。求和最大的子数列直接读取根节点的maxsum就行了。
看了zkw神牛的 《BST拓展与伸展树(splay)一日通》,没学会自顶向下的伸展树,倒学了不少代码技巧。例如建一个null节点,方便自底向上维护节点信息,还有空树不好操作,那就插入两个节点,作为开头和结尾。于是我的代码仅仅245行(4.89K),比起许多块状链表简单多了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | /* * Problem: NOI2005 sequence * Author: Guo Jiabao * Time: 2009.5.30 14:19 * State: Solved * Memo: 伸展树 */ #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> using namespace std; const int MAXN=2850003,MAXL=500001,INF=1001; struct SplayTree { struct SplayNode { SplayNode *c[2],*f; int value,size,sum,maxsum,mls,mrs; bool rev,same; }*root,*null,*lb,*rb,SS[MAXN]; int SC; SplayNode * NewNode(int value,SplayNode *f) { SplayNode *e=SS+ ++SC; e->value=value; e->size=1;e->f=f; e->sum=e->maxsum=e->mls=e->mrs=value; e->same=e->rev=false; e->c[0]=e->c[1]=null; return e; } inline int max(int a,int b){return a>b?a:b;} void update(SplayNode *p) { if (p==null) return; p->size = p->c[0]->size + p->c[1]->size + 1; p->sum = p->c[0]->sum + p->c[1]->sum + p->value; p->mls = p->c[0]->mls; p->mls = max( p->mls , p->c[0]->sum + p->value); p->mls = max( p->mls , p->c[0]->sum + p->value + p->c[1]->mls ); p->mrs = p->c[1]->mrs; p->mrs = max( p->mrs , p->c[1]->sum + p->value); p->mrs = max( p->mrs , p->c[1]->sum + p->value + p->c[0]->mrs ); p->maxsum = p->value; p->maxsum = max( p->maxsum , p->c[0]->maxsum ); p->maxsum = max( p->maxsum , p->c[1]->maxsum ); p->maxsum = max( p->maxsum , p->c[0]->mrs + p->value ); p->maxsum = max( p->maxsum , p->c[1]->mls + p->value ); p->maxsum = max( p->maxsum , p->c[0]->mrs + p->c[1]->mls + p->value ); } void pushdown(SplayNode *p) { if (p==null) return; if (p->rev) { p->rev=false; SplayNode *q=p->c[0]; p->c[0]=p->c[1]; p->c[1]=q; p->c[0]->rev = !p->c[0]->rev; p->c[1]->rev = !p->c[1]->rev; int t=p->mls; p->mls=p->mrs; p->mrs=t; } if (p->same) { p->same=false; p->c[0]->same=p->c[1]->same=true; p->c[0]->value=p->c[1]->value=p->value; p->sum = p->maxsum = p->mls = p->mrs = p->value * p->size; if (p->value < 0) p->maxsum = p->mls = p->mrs = p->value; } } void rotate(SplayNode *x,int o)//Zig o=0 Zag o=1 { SplayNode *y=x->f; pushdown(x->c[0]); pushdown(x->c[1]); pushdown(y->c[!o]); y->c[o] = x->c[!o]; y->c[o]->f=y; x->f = y->f; if (y->f->c[0]==y) y->f->c[0]=x; else y->f->c[1]=x; y->f=x; x->c[!o]=y; update(y); update(x); if (root==y) root=x; } void splay(SplayNode *x,SplayNode *y) { pushdown(x); while (x->f!=y) { if (x->f->f==y) { if (x->f->c[0]==x) rotate(x,0); else rotate(x,1); } else if (x->f->f->c[0] == x->f) { if (x->f->c[0]==x) rotate(x->f,0),rotate(x,0); else rotate(x,1),rotate(x,0); } else { if (x->f->c[1]==x) rotate(x->f,1),rotate(x,1); else rotate(x,0),rotate(x,1); } } } void select(int k,SplayNode *y) { SplayNode *x=root; pushdown(x); for (;k != x->c[0]->size + 1;) { if (k <= x->c[0]->size) x=x->c[0]; else { k-=x->c[0]->size + 1; x=x->c[1]; } pushdown(x); } splay(x,y); } void Insert(int pos,int tot,int *C) { SplayNode *z,*t; z=t=NewNode(C[1],null); for (int i=2;i<=tot;i++) z=z->c[1]=NewNode(C[i],z); select(pos+1,null); select(pos+2,root); root->c[1]->c[0] = t; t->f=root->c[1]; splay(z,null); } void Delete(int pos,int tot) { select(pos,null); select(pos+tot+1,root); root->c[1]->c[0] = null; splay(root->c[1],null); } void MakeSame(int pos,int tot,int value) { select(pos,null); select(pos+tot+1,root); root->c[1]->c[0]->same=true; root->c[1]->c[0]->value=value; splay(root->c[1]->c[0],null); } void Reverse(int pos,int tot) { select(pos,null); select(pos+tot+1,root); root->c[1]->c[0]->rev=!root->c[1]->c[0]->rev; splay(root->c[1]->c[0],null); } int GetSum(int pos,int tot) { select(pos,null); select(pos+tot+1,root); pushdown(root->c[1]->c[0]); return root->c[1]->c[0]->sum; } int MaxSum() { pushdown(root); update(root); return root->maxsum; } void init() { SC=-1; null=0; null=NewNode(-INF,0); null->size=0; lb=root=NewNode(-INF,null); rb=root->c[1]=NewNode(-INF,root); null->sum = lb->sum = rb->sum=0; update(root); } }Splay; int N,M,C[MAXL],pos,i,j,A; char Ctrl[20]; int main() { freopen("seq2005.in","r",stdin); freopen("seq2005.out","w",stdout); Splay.init(); scanf("%d%d",&N,&M); for (i=1;i<=N;i++) scanf("%d",&C[i]); Splay.Insert(0,N,C); for (i=1;i<=M;i++) { scanf("%s",Ctrl); switch (Ctrl[0]) { case 'I': scanf("%d%d",&pos,&N); for (j=1;j<=N;j++) scanf("%d",&C[j]); Splay.Insert(pos,N,C); break; case 'D': scanf("%d%d",&pos,&N); Splay.Delete(pos,N); break; case 'R': scanf("%d%d",&pos,&N); Splay.Reverse(pos,N); break; case 'G': scanf("%d%d",&pos,&N); A=Splay.GetSum(pos,N); printf("%d\n",A); break; case 'M': if (Ctrl[2]=='K') { scanf("%d%d%d",&pos,&N,&C[0]); Splay.MakeSame(pos,N,C[0]); } else printf("%d\n",Splay.MaxSum()); break; } } return 0; } |
维护数列
【问题描述】
请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格)
操作编号 输入文件中的格式 说明
1. 插入
INSERT_posi_tot_c1_c2_…_ctot
在当前数列的第 posi 个数字后插入 tot 个数字:c1, c2, …, ctot;若在数列首插
入,则 posi 为 0
2. 删除 DELETE_posi_tot
从当前数列的第 posi 个数字开始连续 删除 tot 个数字
3. 修改 MAKE-SAME_posi_tot_c
将当前数列的第 posi 个数字开始的连 续 tot 个数字统一修改为 c
4. 翻转 REVERSE_posi_tot
取出从当前数列的第 posi 个数字开始 的 tot 个数字,翻转后放入原来的位置
5. 求和 GET-SUM_posi_tot
计算从当前数列开始的第 posi 个数字 开始的 tot 个数字的和并输出
6. 求和最 大的子列
MAX-SUM
求出当前数列中和最大的一段子列, 并输出最大和
【输入格式】
输入文件的第 1 行包含两个数 N 和 M,N 表示初始时数列中数的个数,M表示要进行的操作数目。
第 2 行包含 N 个数字,描述初始时的数列。
以下 M 行,每行一条命令,格式参见问题描述中的表格。
【输出格式】
对于输入数据中的 GET-SUM 和 MAX-SUM 操作,向输出文件依次打印结果,每个答案(数字)占一行。
【输入样例】
9 8 2 -6 3 5 1 -5 -3 6 3 GET-SUM 5 4 MAX-SUM INSERT 8 3 -5 7 2 DELETE 12 1 MAKE-SAME 3 3 2 REVERSE 3 6 GET-SUM 5 4 MAX-SUM【输出样例】
-1 10 1 10【样例说明】
初始时,我们拥有数列 2 -6 3 5 1 -5 -3 6 3
执行操作 GET-SUM 5 4,表示求出数列中从第 5 个数开始连续 4 个数字之和,1+(-5)+(-3)+6 = -1:
2 -6 3 5 1 -5 -3 6 3执行操作 MAX-SUM,表示要求求出当前数列中最大的一段和,应为 3+5+1+(-5)+(-3)+6+3 = 10:
2 -6 3 5 1 -5 -3 6 3执行操作 INSERT 8 3 -5 7 2,即在数列中第 8 个数字后插入-5 7 2,
2 -6 3 5 1 -5 -3 6 -5 7 2 3执行操作 DELETE 12 1,表示删除第 12 个数字,即最后一个:
2 -6 3 5 1 -5 -3 6 -5 7 2执行操作 MAKE-SAME 3 3 2,表示从第 3 个数开始的 3 个数字,统一修改为 2:
2 -6 3 5 1 -5 -3 6 -5 7 2改为
2 -6 2 2 2 -5 -3 6 -5 7 2执行操作 REVERSE 3 6,表示取出数列中从第 3 个数开始的连续 6 个数:
2 -6 2 2 2 -5 -3 6 -5 7 2如上所示的灰色部分 2 2 2 -5 -3 6,翻转后得到 6 -3 -5 2 2 2,并放回原来位置:
2 -6 6 -3 -5 2 2 2 -5 7 2最后执行 GET-SUM 5 4 和 MAX-SUM,不难得到答案 1 和 10。
2 -6 6 -3 -5 2 2 2 -5 7 2【评分方法】
本题设有部分分,对于每一个测试点:
- 如果你的程序能在输出文件正确的位置上打印 GET-SUM 操作的答案,你可以得到该测试点 60%的分数;
- 如果你的程序能在输出文件正确的位置上打印 MAX-SUM 操作的答案,你可以得到该测试点 40%的分数;
- 以上两条的分数可以叠加,即如果你的程序正确输出所有 GET-SUM 和MAX-SUM 操作的答案,你可以得到该测试点 100%的分数。
请注意:如果你的程序只能正确处理某一种操作,请确定在输出文件正确的位置上打印结果,即必须为另一种操作留下对应的行,否则我们不保证可以正确评分。
【数据规模和约定】
- 你可以认为在任何时刻,数列中至少有 1 个数。
- 输入数据一定是正确的,即指定位置的数在数列中一定存在。
- 50%的数据中,任何时刻数列中最多含有 30 000 个数;
- 100%的数据中,任何时刻数列中最多含有 500 000 个数。
- 100%的数据中,任何时刻数列中任何一个数字均在[-1 000, 1 000]内。
- 100%的数据中,M ≤20 000,插入的数字总数不超过 4 000 000 个,输入文件大小不超过 20MBytes。
orz….
[回复]
BYVoid 回复:
六月 11th, 2009 at 11:42:17
啊 神牛驾到,未曾远迎啊!
[回复]
大牛,这道题能用AVL做吗?
[回复]
BYVoid 回复:
十二月 26th, 2009 at 11:22:56
不可以,因為AVL是必須有鍵值的,Splay不一定需要。
[回复]
SBT写了两天了,写得我口吐白沫。。。
还是WA at 2
问问到底全负数maxsum算啥?
[回复]
BYVoid 回复:
二月 16th, 2010 at 18:53:13
仍然是負數中的最大值
[回复]
恩,这题SBT确实不行,因为那个翻转操作太牛X了。。。不聚在一起确实搞不定。
一开始我写的是SBT上套了类似Splay的操作
WA到不行了,代码长得自己都不想看了= =~
今天把Splay学了才写得AC.
聚集这个东西其他平衡树的确很难做到的,毕竟他们都有自己的规则,不能随意旋转。
同赞Splay太灵活了,不过可惜不能写递归的,代码有点偏长。
[回复]
膜拜!
splay的神奇之处还在于可以想线段树一样下沉标号,这样问题就简单多了。
[回复]
“要注意的是,任何时候只要访问到带标记的节点,一定要标记下传,尤其不要忘了在splay旋转的时候。”
弱弱地问一下,为什么每次访问都要下传?如果不下传,会出现什么问题吗?
[回复]
BYVoid 回复:
三月 22nd, 2010 at 20:56:52
不下傳當然會出問題啊,一旋轉就會導致標記混亂,無法維護。
[回复]
tsinZ1993 回复:
三月 23rd, 2010 at 13:18:21
我错了……我的意思是旋转的时候为什么还要下传呢?既然现在用不到这个点,那不如用的时候在传呗……
[回复]
BYVoid 回复:
三月 23rd, 2010 at 13:45:24
旋转的时候会改变树的结构,如果不下传给两个子树,旋转过后子树就不是它原来的子树的,统计就会出错。这个问题其实你自己演算一下很容易就能明白的。
标记下传的原则就是一旦访问到就要传下去。
[回复]
tsinZ1993 回复:
三月 23rd, 2010 at 15:00:50
原来这样,比如说same标记,如果不下传,当它下传时引用的father^.value就不对了。
膜拜一下……
还有问题就是,允许开始时插入的两个结点成为根吗?他们内部的信息该怎么维护?如果像其他的一样维护,不相当于在原数列的首尾各加了一个本不存在的元素,same时改变它的value,这时mls与mrs两个域就会出错,不是吗?
[回复]
BYVoid 回复:
三月 23rd, 2010 at 16:16:33
懒得解释了,自己好好想想吧。
[回复]
tsinZ1993 回复:
三月 24th, 2010 at 19:39:51
疯了,全部正确,超时8个点
[回复]
六月 30th, 2010 at 11:12:56
[...] 此题用Splay的详细解法请参见BYVoid的解题报告 。我的代码大致与BYVoid的思路相符,但在插入操作时我并没有插成一条链,而是递归将要插入的子序列建成一颗比较平衡的子树。实际测试中,我的代码时间效率比较高,与块状链表标程的用时不相上下,甚至在极限大数据下比标程快。如果用fread快速读入方式,还可以节约近0.5s时间。 [...]
大牛用的什么评测的阿?
[回复]
BYVoid 回复:
八月 12th, 2010 at 16:38:08
個人開發的評測系統。
[回复]
Theo 回复:
八月 12th, 2010 at 16:59:26
可不可以给我一个
[回复]
BYVoid 回复:
八月 12th, 2010 at 17:03:13
如果你對Linux和PHP沒有較爲深入的瞭解,給了你也不會安裝和配置。
[回复]
Theo 回复:
八月 12th, 2010 at 18:05:18
这个你不用担心,我看不懂的还可以找同学阿,他们NOI也刚完
[回复]
BYVoid 回复:
八月 12th, 2010 at 18:07:47
http://code.google.com/p/vakuum-oj/
[回复]
神牛你好。。这题哪里有数据?
[回复]