P2042 [NOI2005]维护数列(Splay 维护序列区间操作,Splay下推标记)

在这里插入图片描述


Splay维护序列练习题,比较麻烦的是标记的下推 和 pushup 操作,两个哨兵节点的初始值应赋为无穷小避免影响操作6的答案。区间更新操作时要pushup 到 root,否则 更新点到 root 维护的信息可能有误

建树: 直接对数组用类似线段树的方法建树,由于要建立哨兵节点,哨兵节点的权值应赋为无穷小。

操作1: 找到起始位置 x,将 x 伸展到 root,并找到 x 的后继 y,y 一定没有左儿子,将要插入的节点依次插入到 后继 y 的左儿子,下一个插入点插入到上一个插入点的右儿子,最后将最后一个点伸展到 root,就完成了,常数也不大,注意向下找后继时要边找边pushdown

操作2: 删除区间 [ l , r ] [l,r] ,将 l 1 l - 1 伸展到 root, r + 1伸展到 l 1 l - 1 的子节点,直接将 r + 1 r + 1 的左儿子整颗子树删掉即可。由于最多插入 4 1 0 6 4*10^6 个节点,会爆空间,删除时将删除的点的编号回收,申请节点时优先使用回收的编号。

操作3: 伸展操作同操作2 ,对 r + 1 r + 1 的左儿子更新并打一个标记,同时 pushup(r + 1) ,pushup(root),这里如果不pushup,后面 r + 1 r + 1 r o o t root 可能就无法根据子节点权值变化维护正确的信息

操作4: 伸展操作同操作2,首先判断有没有操作3的标记,如果有操作3的标记,整颗子树都是同样的权值,没必要翻转。否则先 翻转 r + 1 r + 1 的左儿子的左右子树,并打一个标记,pushup(r + 1),pushup(root),理由同操作3

操作5: 考虑对每个结点维护子树和,询问时伸展操作同操作2,答案就是 ch[r + 1][0] 的子树和

操作6: 同样考虑维护子树的信息,用类似线段树维护最大子段和的方法,每个结点维护 l v , r v , m v lv,rv,mv 表示以其为根,子树区间 的左连续最大子段和,右连续最大子段和,以及整段区间的最大子段和,转移同线段树的转移,答案就是根节点的 m v mv ,但要注意哨兵结点的权值必须赋为无穷小


卡常,要开O2才能过

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e6 + 10;
typedef long long ll;
const ll inf = 1e15;
int n,m;
ll a[maxn];
vector<int> tmp;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
    while ('0'<=ch && ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
struct Splay_tree {
	int ch[maxn][2];		//ch[u][0] 表示 左二子,ch[u][1] 表示右儿子
	int f[maxn];			//当前节点的父节点 
	int sz[maxn]; 			//当前节点的子树的节点个数
	ll val[maxn];			//当前节点的权值
	int cnt[maxn];			//当前节点所表示的值的个数
	int tag[maxn],add[maxn];
	ll sum[maxn],lv[maxn],rv[maxn],mv[maxn]; 
	int root;				//根节点
	int tot;				//总节点的个数 
	queue<int> q;
	inline bool get(int x) {
    	return ch[f[x]][1] == x;
	}
	inline void init() {
		root = tot = 0;
		f[0] = ch[0][0] = ch[0][1] = sz[0] = val[0] = cnt[0] = 0;
		sum[0] = lv[0] = rv[0] = mv[0] = tag[0] = add[0] = 0;
	}
	inline void pushup(int rt) {							//维护 rt 的 sz
		if(rt) {
			sz[rt] = 1;
			lv[rt] = mv[rt] = rv[rt] = sum[rt] = val[rt];
			if(ch[rt][1]) {
				mv[rt] = max(mv[rt],mv[ch[rt][1]]);
				mv[rt] = max(mv[rt],rv[rt] + lv[ch[rt][1]]);
				lv[rt] = max(lv[rt],lv[ch[rt][1]] + sum[rt]);
				rv[rt] = max(rv[ch[rt][1]],sum[ch[rt][1]] + rv[rt]);
				sz[rt] += sz[ch[rt][1]];
				sum[rt] += sum[ch[rt][1]];
			}
			if(ch[rt][0]) {
				mv[rt] = max(mv[rt],mv[ch[rt][0]]);
				mv[rt] = max(mv[rt],rv[ch[rt][0]] + lv[rt]);
				lv[rt] = max(lv[ch[rt][0]],sum[ch[rt][0]] + lv[rt]);
				rv[rt] = max(rv[rt],sum[rt] + rv[ch[rt][0]]);
				sz[rt] += sz[ch[rt][0]];
				sum[rt] += sum[ch[rt][0]];
			}
		}
	}
	inline void pushdown(int x) {					//翻转 rt 的两棵子树
	    int l=ch[x][0],r=ch[x][1];
	    if (add[x]){
	        tag[x]=add[x]=0;//我们有了一个统一修改的标记,再翻转就没有什么意义了
	        if (l)add[l]=1,val[l]=val[x],sum[l]=1ll*val[x]*sz[l];
	        if (r)add[r]=1,val[r]=val[x],sum[r]=1ll*val[x]*sz[r];
	        if (val[x] >= 0) {
	        	if (l)lv[l]=rv[l]=mv[l]=sum[l];
	        	if (r)lv[r]=rv[r]=mv[r]=sum[r];
	        } else {
	        	if (l)lv[l]=rv[l]=mv[l]=val[x];
	        	if (r)lv[r]=rv[r]=mv[r]=val[x];
			}
	    }
	    if (tag[x]){
	        tag[x]=0;
	        if(l) swap(lv[l],rv[l]),swap(ch[l][0],ch[l][1]),tag[l]^=1;
			if(r) swap(lv[r],rv[r]),swap(ch[r][0],ch[r][1]),tag[r]^=1;
	        //注意,在翻转操作中,前后缀的最长上升子序列都反过来了,很容易错
	    }
	}
	inline void newnode(int rt,ll v,int fa) {		//新建节点 
		f[rt] = fa;
		cnt[rt] = sz[rt] = 1;
		add[rt] = tag[rt] = ch[rt][0] = ch[rt][1] = 0;
		val[rt] = sum[rt] = lv[rt] = rv[rt] = mv[rt] = v;
	}
	inline void delnode(int &rt) {
		if(ch[rt][0]) delnode(ch[rt][0]);
		if(ch[rt][1]) delnode(ch[rt][1]); 
		f[rt] = sz[rt] = val[rt] = 0;
		sum[rt] = add[rt] = tag[rt] = ch[rt][0] = ch[rt][1] = 0;
		lv[rt] = rv[rt] = mv[rt] = 0;
		q.push(rt);
		rt = 0;
	}
	inline void build(int &rt,int l,int r,int fa) {
		if (l > r) return ;
		int mid = l + r >> 1;
		rt = ++tot; newnode(rt,a[mid],fa);
		build(ch[rt][0],l,mid - 1,rt);
		build(ch[rt][1],mid + 1,r,rt);
		pushup(rt);
	}
	inline void rotate(int x) {							//旋转操作,根据 x 在 f[x] 的哪一侧进行左旋和右旋 
	    int old = f[x], oldf = f[old];
	    pushdown(old); pushdown(x); 				//如果先推 x,旋完之后 x 仍可能带有标记,这样旋 x 一定没有标记 
	    int whichx = get(x);
	    ch[old][whichx] = ch[x][whichx ^ 1];
	    f[ch[old][whichx]] = old;
	    ch[x][whichx ^ 1] = old; f[old] = x;
	    f[x] = oldf;
	    if(oldf) ch[oldf][ch[oldf][1] == old] = x;
	    pushup(old); pushup(x);						//不要忘记更新
	}
	inline void splay(int x,int goal) {					//将 x 旋到 goal节点  下面
    	for (int fa = f[x]; fa != goal; rotate(x), fa = f[x])	//再把x翻上来
        	if (f[fa] != goal)						//如果fa非根,且x 和 fa是同一侧,那么先翻转fa,否则先翻转x 
            	rotate((get(x)==get(fa))?fa:x);
        if (goal == 0)
			root = x;
	}
	inline int findx(int x) {								//查询第x个数的节点 
		int now = root;
		if (!now)  return 0;
		while(1) {
			pushdown(now);							//每次下推标记 
			if(ch[now][0] && sz[ch[now][0]] >= x) {
				now = ch[now][0];
			} else {
				int sum = cnt[now] + (ch[now][0] ? sz[ch[now][0]] : 0);
				if (x <= sum) return now;
				x -= sum, now = ch[now][1];
			}
		}
		return now;
	}
    inline void del(int l,int r) {					//仅针对 P2042 [NOI2005]维护数列的删除操作 ,删除[l,r]之间的点 
    	int x = findx(l - 1), y = findx(r + 1);
    	splay(x,0);
    	splay(y,x);
    	delnode(ch[ch[root][1]][0]);
    	pushup(ch[root][1]);
    	pushup(root);
	}
	inline void insert(int x) {				//在 第 x 个数之后插入一个值 v 
		int fx = findx(x);
		splay(fx,0);
		int k = ch[root][1];
		pushdown(k);
		while (ch[k][0]) {
			k = ch[k][0];
			pushdown(k);
		}
		int p = root;
		for (int i = 0; i < tmp.size(); i++) {
			if(!q.empty()) p = q.front(), q.pop();
			else p = ++tot;
			newnode(p,tmp[i],k);
			if (i == 0) {
				ch[k][0] = p;
				k = ch[k][0];	
			} else {
				ch[k][1] = p;
				k = ch[k][1];
			}
		}
		tmp.clear();
		splay(k,0);
	}
	inline void update(int l,int r,int c) {		//将 l,r区间内的值修改为 c 
		int x = findx(l - 1), y = findx(r + 1);
    	splay(x,0);
    	splay(y,x);
    	int p = ch[ch[root][1]][0];
    	add[p] = 1; val[p] = c;
    	sum[p] = 1ll * sz[p] * c;
    	if(c >= 0) lv[p] = rv[p] = mv[p] = sum[p];
    	else lv[p] = rv[p] = mv[p] = c;
    	pushup(y); pushup(f[y]);
	}
	inline void change(int l,int r) {
		int x = findx(l - 1), y = findx(r + 1);
    	splay(x,0);
    	splay(y,x);
    	int p = ch[ch[root][1]][0];
    	if(!add[p]) {
	    	swap(ch[p][0],ch[p][1]);
	    	swap(lv[p],rv[p]);
	    	tag[p] ^= 1;
	    	pushup(y); pushup(x);
	    }
	}
	ll ask_sum(int l,int r) {
		int x = findx(l - 1), y = findx(r + 1);
    	splay(x,0);
    	splay(y,x);
    	return sum[ch[ch[root][1]][0]];
	}
	ll max_sum() {
		return mv[root];
	}
}tree;
char s[maxn];
int main() {
	n = read(); m = read();
	for (int i = 2; i <= n + 1; i++)
		a[i] = read();
	a[1] = a[n + 2] = -inf;
	tree.init();
	tree.build(tree.root,1,n + 2,0);
	int cnt = 0;
	while(m--) {
		cnt++;
		scanf("%s",s);
		if(strcmp(s,"INSERT") == 0) {
			int x,y,z; x = read(); y = read();
			x++;
			for (int i = 1; i <= y; i++) {
				z = read();
				tmp.push_back(z);
			}
			tree.insert(x);
		} else if(strcmp(s,"GET-SUM") == 0) {
			int x,y; x = read(); y = read();
			int l = x + 1, r = x + y;
			printf("%lld\n",tree.ask_sum(l,r));
		} else if(strcmp(s,"MAX-SUM") == 0) {
			printf("%lld\n",tree.max_sum());
		} else if(strcmp(s,"DELETE") == 0) {
			int x,y; x = read(); y = read();
			int l = x + 1, r = x + y;
			tree.del(l,r);
		} else if(strcmp(s,"MAKE-SAME") == 0) {
			int x,y,v; x = read(); y = read(); v = read();
			int l = x + 1, r = x + y;
			tree.update(l,r,v);
		} else if(strcmp(s,"REVERSE") == 0) {
			int x,y; x = read(); y = read();
			int l = x + 1, r = x + y;
			tree.change(l,r);
		}
	}
	return 0;
}
发布了332 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/104180777
今日推荐