首页 > 其他分享 >[题解]P2042 [NOI2005] 维护数列 - Splay解法

[题解]P2042 [NOI2005] 维护数列 - Splay解法

时间:2024-06-23 20:54:45浏览次数:25  
标签:lc int 题解 sum rmax lmax Splay NOI2005 rc

P2042 [NOI2005] 维护数列

一道思路不难,但实现细节很多的平衡树题,调了一天半终于做出来了w。


对于初始序列,我们可以直接构建一条链(毕竟一个一个调用插入函数也可能形成一条链)。题解有递归直接构建成一棵严格平衡的二叉树的,这样也可以,常数可能会小一点。

其中区间反转就是裸的文艺平衡树题解)。

区间修改&求和与文艺平衡树的原理相同,只需要多加\(1\)个赋值的懒标记即可。

删除都是区间操作。我们受文艺平衡树的启发,找到区间的左右端点\(l,r\),然后再找到\(l\)在中序遍历中的前驱节点\(L\),\(r\)在中序遍历中的后继节点\(R\),splay(L,0),在splay(R,L),这样\(R\)的左子树就是要操作的区间\([l,r]\)了。删除\([l,r]\)区间,直接断开\(R\)与其左子树的连接即可。

插入同样是区间操作,我们可以把输入的值连成一条链(或构建成一棵严格平衡的二叉树),然后找我们要插入的位置是哪一个节点,设要插入的位置为\(pos\)之后,那么找到的节点\(u\)就是中序遍历第\(pos\)个节点。我们再寻找\(u\)的后继节点,把这条链接到后继它上面就可以了(别忘了无右儿子的情况)。

平衡树维护最大子列和其实不难,但这道题有个很毒的坑点:子列不能为空,然而题面却没有给出!

每个节点\(u\)额外维护\(3\)个信息:

  • \(lmax\):子树\(u\)的中序遍历的最大前缀(可以为空)。
  • \(rmax\):子树\(u\)的中序遍历的最大后缀(可以为空)。
  • \(maxx\):子树\(u\)的中序遍历的最大子列和(不能为空)。

初始化(\(sum\)表示子树权值和):
\(maxx(u)=v(u)\\lmax(u)=rmax(u)=\max(v(u),0)\)

转移:

  • \(\color{green}lmax(u)=\max\{lmax(lc(u)),sum(lc(u))+v(u)+lmax(rc(u)),0\}\)
    (如果不选\(u\)就是\(lmax(lc(u))\),如果选\(u\)就是\(sum(lc(u))+v(u)+lmax(rc(u))\),根据定义可以为空,所以还要和\(0\)取一个\(\max\))
  • \(\color{darkcyan}rmax(u)=\max\{rmax(rc(u)),sum(rc(u))+v(u)+rmax(lc(u)),0\}\)
    (同理)
  • \(\color{mediumblue}maxx(u)=\max\{rmax(lc(u))+v(u)+lmax(rc(u)),maxx(lc(u)),maxx(rc(u))\}\)
    (如果不选\(u\)就直接等于左边的答案/右边的答案,如果选\(u\)就是左边的后缀最大+\(u\)的权值+右边的前缀最大)

pushdown中的转移(set_sum)见代码。

注意到转移的第\(3\)个式子中,\(rmax(lc(u))+v(u)+lmax(rc(u))\)是选\(u\)的情况,所以前后缀可以为空。


其实就这些了,思路不难,不过代码实现有很多需要注意。如果遇到问题可以查一下:

  • 子列不能为空。
  • 两个哨兵值要置为\(0\),否则计算\(sum\)可能会出问题。
  • 一定一定要pushdown,在查找第\(k\)名、插入节点的过程中都要pushdown
  • 插入次数是\(4*10^6\),如果开这么大的数组会MLE。但题目保证任何时候平衡树里不超过\(5*10^5\)个元素。我们考虑把删掉的空间重复利用:每删除一个子树,就把这个子树的根节点入栈,为插入的节点分配空间时,先看栈中有没有节点,如果有,则把该节点拿出来,把它的左右子节点(如果有)入栈。没有必要删除后一次性把删掉的节点都入栈。
  • 区间翻转需要lmaxrmax交换位置。
  • 和线段树相似地,下放标记时对左儿子、右儿子操作,而非自己。拿计算\(sum\)来距离,pushdown(x)应该是更新左右孩子的\(sum\),而自己的\(sum\)应该在主函数中完成设置。其他同理。这应该作为写pushdown的一个习惯。因为代码中,很有可能调用pushdown(x)后就去获得\(x\)子节点的各种信息(比如ins函数中pushdown(u)后就去获取\(lc(rc(u))\)),所以必须保证子节点状态得到更新。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 500010
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]
#define ch(x,y) tr[x].ch[y]
#define fa(x) tr[x].fa
#define v(x) tr[x].v
#define siz(x) tr[x].siz
#define tag(x) tr[x].tag
#define tag2(x) tr[x].tag2
#define sum(x) tr[x].sum
#define lmax(x) tr[x].lmax
#define rmax(x) tr[x].rmax
#define maxx(x) tr[x].maxx
using namespace std;
struct tree{
	int ch[2],siz,fa,v,tag2,sum;
	int lmax,rmax,maxx;
	bool tag;
	void init(){ch[0]=ch[1]=siz=fa=v=lmax=rmax=maxx=tag=0;tag2=LLONG_MIN;}
}tr[N];
int n,m,cnt,root,nodecnt;
stack<int> fr;
void init(int u){tr[u].init();}
int newnode(int v,int siz=1){
	int u;
	if(fr.empty()) u=++cnt;
	else{
		u=fr.top(),fr.pop();
		if(lc(u)) fr.push(lc(u));
		if(rc(u)) fr.push(rc(u));
	}
	init(u);
	sum(u)=v(u)=maxx(u)=v,siz(u)=siz;
	lmax(u)=rmax(u)=max(v,0ll);
	return u;
}
void update(int u){
	siz(u)=siz(lc(u))+siz(rc(u))+1;
	sum(u)=sum(lc(u))+v(u)+sum(rc(u));
	lmax(u)=max(max(lmax(lc(u)),sum(lc(u))+v(u)+lmax(rc(u))),0ll);
	rmax(u)=max(max(rmax(rc(u)),sum(rc(u))+v(u)+rmax(lc(u))),0ll);
	maxx(u)=rmax(lc(u))+v(u)+lmax(rc(u));
	if(lc(u)) maxx(u)=max(maxx(u),maxx(lc(u)));
	if(rc(u)) maxx(u)=max(maxx(u),maxx(rc(u)));
}
bool get(int u){return u==rc(fa(u));}
void rot(int x){
	int y=fa(x),z=fa(y);//保证y!=0
	bool dir=get(x),tdir=get(y);
	if(ch(x,!dir)) fa(ch(x,!dir))=y;
	ch(y,dir)=ch(x,!dir);
	ch(x,!dir)=y;
	fa(y)=x,fa(x)=z;
	if(z) ch(z,tdir)=x;
	update(y),update(x);
}
void splay(int x,int y){
	for(int f;(f=fa(x))!=y;rot(x))
		if(fa(f)!=y) rot(get(f)==get(x)?f:x);
	if(!y) root=x;
}
void set_sum(int x,int v){//用于pushdown和主函数
	tag2(x)=v,v(x)=v,sum(x)=v*siz(x);
	if(v>0) lmax(x)=rmax(x)=maxx(x)=sum(x);
	else lmax(x)=rmax(x)=0,maxx(x)=v;
}
//用于pushdown和主函数
void set_rev(int x){swap(lc(x),rc(x)),swap(lmax(x),rmax(x)),tag(x)^=1;}
void pushdown(int x){
	if(tag2(x)!=LLONG_MIN){
		if(lc(x)) set_sum(lc(x),tag2(x));
		if(rc(x)) set_sum(rc(x),tag2(x));
		tag(x)=0;//相当于一个小优化,如果已经区间赋值了,那么翻转不翻转无所谓了
		tag2(x)=LLONG_MIN;
	}
	if(tag(x)){
		if(lc(x)) set_rev(lc(x));
		if(rc(x)) set_rev(rc(x));
		tag(x)=0;
	}
}
int find(int num){//找中序遍历第num个是哪个节点
	int u=root;
	while(u){
		pushdown(u);//别忘了pushdown
		if(siz(lc(u))+1==num) break;
		else if(siz(lc(u))>=num) u=lc(u);
		else num-=siz(lc(u))+1,u=rc(u);//这两句顺序别反了
	}
	return u;
}
void ins(int num,int v){//在中序第num后插入子树v
	int u=find(num+1);
	pushdown(u);//pushdown不要忘记
	int nex=rc(u);
	if(!nex) rc(u)=v,fa(v)=u,splay(u,0);
	else{
		while(lc(nex)){
			pushdown(nex);//一定记得写pushdown
			nex=lc(nex);
		}
		pushdown(nex);//这个也不要忘
		lc(nex)=v,fa(v)=nex,splay(nex,0);
	}
}
void make_range(int& l,int& r){//取出[l,r]的区间,放在lc(r)中
	l=find(l),r=find(r+2);//本来应该是l-1,r+1,但因为有哨兵节点所以需要+1
	splay(l,0),splay(r,l);//lc(r)就是要操作的部分
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n>>m;
	newnode(0,1);//两个哨兵的值都为0,否则sum计算会出问题
	for(int i=1;i<=n+1;i++){//连成一条链
		int v;
		if(i==n+1) v=0;
		else cin>>v;
		newnode(v);
		lc(cnt)=cnt-1;
		fa(cnt-1)=cnt;
		update(cnt);
	}
	root=nodecnt=n+2;//根节点不是1而是n+2
	while(m--){
		string op;
		cin>>op;
		if(op=="INSERT"){
			int pos,tot,x;
			cin>>pos>>tot;
			if(tot==0) continue;
			int cur,last;
			for(int i=1;i<=tot;i++){//把输入连成一条链
				cin>>x;
				cur=newnode(x,i);
				if(i>1) fa(last)=cur,lc(cur)=last;
				update(cur);
				last=cur;
			}
			ins(pos,cur);//cur是这条链的根节点
			nodecnt+=tot;
		}else if(op=="DELETE"){
			int l,tot,r;
			cin>>l>>tot;
			r=l+tot-1;
			make_range(l,r);
			if(lc(r)) fa(lc(r))=0,fr.push(lc(r)),lc(r)=0;//需要将子树根节点入栈
			nodecnt-=tot;
		}else if(op=="MAKE-SAME"){
			int l,tot,r,v;
			cin>>l>>tot>>v;
			r=l+tot-1;
			make_range(l,r);
			if(lc(r)) set_sum(lc(r),v);
		}else if(op=="REVERSE"){
			int l,tot,r;
			cin>>l>>tot;
			r=l+tot-1;
			make_range(l,r);
			if(lc(r)) set_rev(lc(r));
		}else if(op=="GET-SUM"){
			int l,tot,r;
			cin>>l>>tot;
			r=l+tot-1;
			make_range(l,r);
			cout<<sum(lc(r))<<"\n";
		}else if(op=="MAX-SUM"){
			int l=1,r=nodecnt-2;
			make_range(l,r);
			cout<<maxx(lc(r))<<"\n";
		}
	}
	return 0;
}

标签:lc,int,题解,sum,rmax,lmax,Splay,NOI2005,rc
From: https://www.cnblogs.com/Sinktank/p/18263785

相关文章

  • [MdOI R5] Many Minimizations & [ARC164F] Many Increasing Problems 题解
    讲下一个思路比较自然的基于自然数幂和的\(O(n\logn)\)且复杂度与\(m\)几乎无关的做法。不难发现让我们计数的问题是保序回归\(L_1\)中一条链的情况。这个情况有一个简单的slope-trick做法:用堆维护斜率,每次push进去两个当前的数,然后pop出一个最大值。最终所有数的和......
  • [MdOI R5] Many Minimizations & [ARC164F] Many Increasing Problems 题解
    讲下一个思路比较自然的基于自然数幂和的\(O(n\logn)\)且复杂度与\(m\)几乎无关的做法。不难发现让我们计数的问题是保序回归\(L_1\)中一条链的情况。这个情况有一个简单的slope-trick做法:用堆维护斜率,每次push进去两个当前的数,然后pop出一个最大值。最终所有数的和......
  • C++题解(1) 信息学奥赛一本通 1003:对齐输出 洛谷 B2004:对齐输出 土豆编程 T1003:对
    【题目描述】读入三个整数,按每个整数占8个字符的宽度,右对齐输出它们,按照格式要求依次输出三个整数,之间以一个空格分开。【输入】只有一行,包含三个整数,整数之间以一个空格分开。【输出】只有一行,按照格式要求依次输出三个整数,之间以一个空格分开。【输入样例】......
  • 一些东西 题解
    ATBAB设\(f_{i,0/1}\)表示\(i\)子树DFS序奇/偶位置和的最大值,首先如果\(i\)所有孩子的子树大小都是偶数,那访问这些孩子的顺序就无所谓了,否则考虑以\(i\)的至少一个大小为奇数的孩子为分界,对所有大小为偶数的孩子\(v\),把\(f_{v,0}\)更大的\(v\)、\(f_{v,1}\)......
  • [题解]CF311B Cats Transport
    思路首先,对于每一只小猫刚好玩完就被饲养员接走的出发时间必定为\(t_i-sd_i\)。那么,我们令\(a_i=t_i-sd_i\)表示第\(i\)只小猫的最早出发时间。因此,对于第\(k\)时刻出发的饲养员能接到的小猫当且仅当满足\(a_i\leqk\)。然后,我们定义\(dp_{i,j}\)表示用\(i\)......
  • [题解]CF245H Queries for Number of Palindromes
    思路定义\(dp_{i,j}\)表示区间\([i,j]\)中回文串的数量。那么,不难得出状态转移方程\(dp_{i,j}=dp_{i-1}+f_{i,j}\)。(其中\(f_{i,j}\)表示左端点大于等于\(i\),右端点为\(j\)的回文串数量)由此,现在问题转变为了如何求\(f_{i,j}\)。如果我们在求出了\(f_{i+1,j}......
  • [题解]CF154B Colliders
    思路首先我们将两种操作分开讨论:Part1加入操作那么,我们可以用一个数组\(vis_i=0/1\)表示\(i\)是关闭/开启状态,\(p_i\)表示因数有\(i\)的数。如果$vis_x=1$,说明此机器在之前已经启动过了,输出Success。然后,对\(x\)分解质因数,将质因数全部塞进一个集合\(a\)......
  • [题解]AT_dp_w Intervals
    思路首先考虑较为普通的DP。定义\(dp_{i,j}\)表示在前\(i\)个位置中,最后一个1在\(j\)的最大分数,显然有:\[dp_{i,j}=\left\{\begin{matrix}\max_{k=1}^{i-1}\{dp_{i-1,k}\}+\sum_{l_k\leqj\wedger_k=i}{a_k}&(i=j)\\dp_{i-1,j}+\sum......
  • [题解]AT_arc138_a [ARC138A] Larger Score
    思路不难发现:对于每一个\(i(1\leqi\leqk)\),如果能在\((k+1)\simn\)中找到任何一个\(j\),满足\(a_j>a_i\)就算满足条件。进一步思考,为了使操作数最小,对于每一个\(1(1\leqi\leqk)\),都找一个在\((k+1)\simn\)中第一个大于\(a_i\)的数,便于它交换。那么......
  • [题解]AT_arc116_d [ARC116D] I Wanna Win The Game
    思路因为题目与二进制有关,考虑往二进制的方向思考。定义\(dp_{i,j}\)表示在所有的\(n\)个数中,当前在决策对于每一个数在二进制表示下的第\(i\)位是\(0\)还是\(1\),且和为\(j\)的方案数。因为异或需要满足对于所有\(a_i\)表示为二进制后每一位\(1\)的个数均为偶数......