[BZOJ1500]NOI2005 维护数列|splay

这题号称是noi出过最变态的数据结构题,,感觉还差不多嘛。。一开始我一直在纠结splay上的节点一点要有一个key值的啊,要是以这个数在序列中的位置作为key值的话又要插入又要删除肯定弄不了。。然后想了很久突然顿悟。。貌似我给一个初始顺序以后是不用访问key的&&找第k位的数只要写一个findkth就行了嘛。。(当时太弱)

然后我就一口气写了下来。。

对每个节点维护size,sum,maxsum,maxpre,maxpos(这两个用于更新maxsum),same,rev(这两个是lazy标记)。

更新maxsum考虑3种情况,一是左儿子的maxsum,二是右儿子的maxsum,三是左儿子的maxpos+自身num+右儿子maxpre,这里maxpre和maxpos更新的时候是可以一个都不要也就是为0的,我就在这里被坑了。。

1.      初始或者插入的时候建树用类似线段树的二分的方法,然后把pos转到root,把pos+1转到root下,只要把数挂在pos+1的左儿子下就行了。。这里有一个关于findkth的问题,为了方便处理边界我一开始插入了一个头结点和一个尾结点,findkth的时候要小心头结点的存在。。

2.      删除的时候把pos转到root,pos+tot+1转到root下,pos+tot+1的左儿子就是要删除的区间,直接删去即可。。这里要注意一个回收内存的问题,要把删去的结点放到一个栈里,newnode的时候优先从这个栈里拿结点,这样既可以节省内存又可以节省时间。。

3.      修改和翻转的时候找出区间打上标记就行。

4.      getsum找出区间直接输出答案,maxsum直接输出root的答案。

打到这里最原始的程序就写完了,但是我一开始在处理标记的时候只是单纯的打上标记没有更新维护的东西,导致每次输出答案必须遍历子树更新答案,时间复杂度没有了保证。。然后我看黄学长的代码发现打标记的时候要把信息及时维护一下,一加上去速度瞬间就快了起来。。(当时太弱)

第二个坑点是一开始插入的头结点尾结点的值要是-inf不能是0,否则maxsum更新时会出错。。

最后就是same标记的问题,要特殊处理一下makesame(0)的情况。。

到这里就搞定了,6052MSAC,有点慢。。

#include<iostream>
#include<cstdio>
#include<memory.h>
using namespace std;
const int inf=99999999,maxn=500005;
int n,m,i,nnode=0,rest=0,root=0,root1,pos,tot,k,q[maxn],c[maxn][2],size[maxn],pre[maxn],num[maxn],maxsum[maxn],maxpre[maxn],maxpos[maxn],same[maxn],rev[maxn],sum[4000005],s[maxn];
char str[10];
void update(int x)
{
	size[x]=size[c[x][0]]+size[c[x][1]]+1;sum[x]=sum[c[x][0]]+sum[c[x][1]]+num[x];
	maxsum[x]=max(max(maxsum[c[x][0]],maxsum[c[x][1]]),maxpos[c[x][0]]+num[x]+maxpre[c[x][1]]);
	maxpre[x]=max(sum[c[x][0]]+maxpre[c[x][1]]+num[x],maxpre[c[x][0]]);maxpos[x]=max(sum[c[x][1]]+maxpos[c[x][0]]+num[x],maxpos[c[x][1]]);
}

void marksame(int x,int c)
{
	same[x]=c;
	if (c==1001) c=0;
	num[x]=c;sum[x]=size[x]*c;
	if (c>=0) maxsum[x]=maxpre[x]=maxpos[x]=size[x]*c; else maxpre[x]=maxpos[x]=0,maxsum[x]=c;
}
void markrev(int x)
{
	swap(c[x][0],c[x][1]);
	swap(maxpre[x],maxpos[x]);
	rev[x]^=1;
}
void down(int x)
{
	if (same[x])
	{
		if (c[x][0]) marksame(c[x][0],same[x]); if (c[x][1]) marksame(c[x][1],same[x]);
		same[x]=0;
	}
	if (rev[x])
	{
		if (c[x][0]) markrev(c[x][0]);if (c[x][1]) markrev(c[x][1]);
		rev[x]=0;
	}
}
void newnode(int &x,int fa,int k)
{
	if (rest) x=s[rest--];else x=++nnode;
	pre[x]=fa;num[x]=k;	size[x]=1;
	c[x][0]=c[x][1]=same[x]=rev[x]=0;
	sum[x]=maxsum[x]=k;
	maxpre[x]=maxpos[x]=max(k,0);
}
void rot(int x,int kind)
{
	int y=pre[x];int z=pre[y];down(x);
	c[y][!kind]=c[x][kind];pre[c[x][kind]]=y;
	c[x][kind]=y;pre[y]=x;
	pre[x]=z;if (z) c[z][c[z][1]==y]=x;
	update(y);update(x);if (z) update(z);
}
void splay(int x,int goal)
{
	int y,z,kind;
	while (pre[x]!=goal)
	{
		y=pre[x];down(y);
		if (pre[y]==goal) rot(x,c[y][0]==x);
		else
		{
			z=pre[y];down(z);kind=c[z][0]==y;
			if (c[y][!kind]==x) rot(y,kind);else rot(x,!kind);
			rot(x,kind);
		}
	}
	if (goal==0) root=x;
}
int findkth(int x,int k)//加处理
{
	down(x);
	if (size[c[x][0]]>=k) return findkth(c[x][0],k);
	if (k==size[c[x][0]]+1) return x;
	if (k>size[c[x][0]]+1) return findkth(c[x][1],k-size[c[x][0]]-1);
}
void build(int &x,int fa,int l,int r)
{
	if (l>r) return;
	int mid=(l+r)/2;
	newnode(x,fa,q[mid]);
	if (l==r) return;
	build(c[x][0],x,l,mid-1);build(c[x][1],x,mid+1,r);
	update(x);
}
void insert(int pos,int tot)
{
	int i;
	for (i=1;i<=tot;i++)
		scanf("%d",&q[i]);
	build(root1,0,1,tot);
	splay(findkth(root,pos+1),0);
	splay(findkth(root,pos+2),root);
	c[c[root][1]][0]=root1;pre[root1]=c[root][1];
	update(c[root][1]);update(root);
}
void erase(int x)
{
	if (!x) return;
	s[++rest]=x;
	erase(c[x][0]);erase(c[x][1]);
}
void deleted(int pos,int tot)
{
	splay(findkth(root,pos),0);splay(findkth(root,pos+tot+1),root);
	erase(c[c[root][1]][0]);
	c[c[root][1]][0]=0;
	update(c[root][1]);update(root);
}
void makesame(int pos,int tot,int k)
{
	if (k==0) k=1001;
	splay(findkth(root,pos),0);splay(findkth(root,pos+tot+1),root);
	marksame(c[c[root][1]][0],k);
	update(c[root][1]);update(root);
}
void markrev(int pos,int tot)
{
	splay(findkth(root,pos),0);splay(findkth(root,pos+tot+1),root);
	markrev(c[c[root][1]][0]);
	update(c[root][1]);update(root);

}
int getsum(int pos,int tot)
{
	splay(findkth(root,pos),0);splay(findkth(root,pos+tot+1),root);
	return sum[c[c[root][1]][0]];
}
void print(int x)
{
	if (c[x][0]) print(c[x][0]);
	printf("%d->num:%d,fa:%d,lc:%d,rc:%d,size:%d,sum:%d,maxpre:%d,maxpos:%d,maxsum:%d,same:%d,rev:%d\n",x,num[x],pre[x],c[x][0],c[x][1],size[x],sum[x],maxpre[x],maxpos[x],maxsum[x],same[x],rev[x]);
	if (c[x][1])print(c[x][1]);
}
int main()
{
	freopen("shulie.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&m);
	newnode(root,0,-inf);newnode(c[root][1],root,-inf);
	insert(0,n);
	size[0]=pre[0]=same[0]=rev[0]=num[0]=sum[0]=maxpre[0]=maxpos[0]=0;maxsum[0]=-inf;
	for (i=1;i<=m;i++)
	{
		scanf("%s",&str);
		if (str[2]!='X') scanf("%d%d",&pos,&tot); else printf("%d\n",maxsum[root]);
		if (str[0]=='I') insert(pos,tot);
		if (str[0]=='D') deleted(pos,tot);
		if (str[0]=='M'&&str[2]=='K') scanf("%d",&k),makesame(pos,tot,k);
		if (str[0]=='R') markrev(pos,tot);
		if (str[0]=='G') printf("%d\n",getsum(pos,tot));
	}
}
时间: 2024-08-11 01:28:21

[BZOJ1500]NOI2005 维护数列|splay的相关文章

BZOJ1500: [NOI2005]维修数列[splay ***]

1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 12278  Solved: 3880[Submit][Status][Discuss] Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目.第2行包含N个数字,描述初始时的数列.以下M行,每行一条命令,格式参见问题描述中的表格.任何时刻数列中最多含有500 000个数,

[模板]洛谷T2042 NOI2005 维护数列 Splay

PS:某大佬讲,当心情特别好or特别不好的时候,可以来攻略这个题...果然萌新足足被这题恶心了半个月... 进入正题: 这道题题意如此之裸-Splayの究极进化,那么就没有什么好讲的,直接说搞法好了... 为了代码写起来方便,萌新封装得可能有些过,若不合诸位大佬的口味还请见谅~ 节点node结构体定义: key:节点原值:size:子树大小:ch[2]:子树指针: set_pd:记录是否打了MAKE-SAME的懒标记:setv:MAKE-SAME的修改值:turn:记录是否旋转: sum:子树元

[bzoj1500][NOI2005 维修数列] (splay区间操作)

Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目. 第2行包含N个数字,描述初始时的数列. 以下M行,每行一条命令,格式参见问题描述中的表格. 任何时刻数列中最多含有500 000个数,数列中任何一个数字均在[-1 000, 1 000]内. 插入的数字总数不超过4 000 000个,输入文件大小不超过20MBytes. Output 对于输入数据中的GET-SUM和MAX-SUM操作,向输出文件依次

数据结构(Splay平衡树):COGS 339. [NOI2005] 维护数列

339. [NOI2005] 维护数列 ★★★★☆   输入文件:seq2005.in   输出文件:seq2005.out   简单对比 时间限制:3 s   内存限制:256 MB [问题描述] 请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格) 操作编号 输入文件中的格式 说明 1.  插入 INSERT_posi_tot_c1_c2_..._ctot 在当前数列的第 posi 个数字后插入 tot 个数字:c1, c2,

NOI2005维护数列

1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 6263  Solved: 1879[Submit][Status] Description Input 输入文件的第1行包含两个数N和M,N表示初始时数列中数的个数,M表示要进行的操作数目.第2行包含N个数字,描述初始时的数列.以下M行,每行一条命令,格式参见问题描述中的表格. Output 对于输入数据中的GET-SUM和MAX-SUM操作,向输出文件依次打印

BZOJ 1500: [NOI2005]维修数列( splay )

splay..... ------------------------------------------------------------------------ #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #define rep( i , n ) for( int i = 0 ; i < n ; ++

BZOJ1500 NOI2005 维修数列 平衡树

题意:给定一个数列,要求维护:1.在p之后加入tot个数  2.删除p之后tot个数  3.将p之后tot个数修改为c  4.翻转p之后tot个数  5.输出p之后tot个数的和  6.输出整个数列的最大子段和. 题解:平衡树很经典的题目了……主要说一下4和6操作,4的话因为翻转操作是可以分治的,所以可以用翻转标记:6我们维护一个节点所维护的区间的的最大子段和ms.从左开始的最大子段和lms.从右开始的最大子段和rms,显然有ms=max(lchild->ms,rchild->ms,lchil

【bzoj1500】维修数列——splay

splay的高级题目,有splay的全部操作,然而本蒟蒻竟不自量力地把这道题作为splay的入门题,然后就学(mi)习(man)了一个星期-- 第一次是对着cyc的模版码的,万分感谢>< 第二次就自己码了一个小时(弱......) 就作为一个splay学习的模版吧! #include<cstdio> #include<cstring> #include<iostream> #include<queue> const int maxn=5e5+10

HYSBZ 1500 [NOI2005]维修数列 splay

解题思路:终于把这道splay神题A掉了,splay专题也算是告一段落了,这个题主要的坑点,还是旋转和区间合并结合. 解题代码: 1 // File Name: hysbz1500.cpp 2 // Author: darkdream 3 // Created Time: 2015年04月10日 星期五 10时41分03秒 4 5 #include<vector> 6 #include<list> 7 #include<map> 8 #include<set>