NOI2005 维修数列(splay)

题意:写一个程序维护一个序列,支持6个操作:插入一段序列,删除一段子序列,区间统一修改为一个值,区间翻转,求区间和,求整个序列的最大非空子序列(其实求区间的最大非空子序列也可以)。序列中最多同时存在5*10^5个元素。

想法:看到区间翻转就知道是splay,但这题非常恶心,要维护大量的信息,懒标记控制不当也容易写错。

这题比较有价值的地方在于分治求区间最大和,需要像线段树的hotel一题一样维护区间靠左边,靠右边,以及整个区间的最大和,然后由子树update的时候要注意,就是splay的节点实际也带有一个值,合并的时候要考虑进来,这一点和线段树有点不同。

然后当题目涉及一个以上懒标记的时候,一定要注意多个懒标记之间关系,是先后,还是并列,还是覆盖。

然后很重要但常常被忽略的一个问题,就是懒标记的性质:是读到懒标记修改当前节点信息,还是读到懒标记修改儿子节点信息。

前者比较容易写,把区间旋转出来然后打上标记返回,后者麻烦一点,要修改那个节点的信息,再打上标记再返回。

区间翻转的rev标记很好处理,随便怎么弄一般都不会出错。但其他的,例如求区间和之类的尽量用后者,今天调了很久都WA,然后把懒标记换成后面那种就A了,可见如果使用前者而在某些地方漏掉pushdown后果很严重,而后者有些地方可以不写pushdown。

还有一点,这题需要不断地插入和删除,内存开销很蛋疼。网上很多人是用循环队列或者栈在删除操作的时候遍历子树暴力回收内存,而我建了两棵splay,一棵是序列,一棵是回收站,每次删除或者插入的时候在两棵splay之间split和merge即可。注意回收的节点很可能带有删除之前打的懒标记,下次提取的时候要清空。


#include<cstdio>
#include<cstring>
#include<algorithm>

inline void get(int &r) {
	char c, b=0; r=0;
	do{c=getchar();if(c=='-')b=1;}while (c<'0'||c>'9');
	do{r=r*10+c-'0';c=getchar();}while (c>='0'&&c<='9');
	if (b) r = -r;
}

const int MAXN = 500010;
const int inf = 1e9;
using namespace std;
#define lch(x) x->ch[0]
#define rch(x) x->ch[1]

int N, M;
struct Node {
	int sz, lmx, rmx, mx, sum, v;
	bool rev, sam; //即时生效
	Node*fa, *ch[2];
	Node () {
		lmx = rmx = mx = -inf;
		rev = sam = 0;
		sum = sz = v = 0;
	}
} nil, *NIL = &nil, *r1, *r2, nds[MAXN];
//r1为序列,r2为回收站
int arr[MAXN];

inline void upsm(Node*x, int v)
{
	x->v = v;
	x->sum = v * x->sz;
	x->mx = x->lmx = x->rmx = max(x->sum, v);
	x->sam = 1;
}
inline void uprv(Node*x)
{
	swap(lch(x), rch(x));
	swap(x->lmx, x->rmx);
	x->rev ^= 1;
}

inline void pushdown(Node*x)
{
	if (x->sam) {
		x->sam = x->rev = 0;
		if (lch(x) != NIL) upsm(lch(x), x->v);
		if (rch(x) != NIL) upsm(rch(x), x->v);
	}
	if (x->rev) {
		x->rev = 0;
		if (lch(x) != NIL) uprv(lch(x));
		if (rch(x) != NIL) uprv(rch(x));
	}
}

inline void pushup(Node*x)
{
	Node*&L = lch(x), *&R = rch(x);
	x->sz = L->sz + R->sz + 1;
	x->sum = L->sum + R->sum + x->v;
	x->lmx = max(L->lmx, L->sum + x->v + max(R->lmx, 0));
	x->rmx = max(R->rmx, R->sum + x->v + max(L->rmx, 0));
	x->mx = max(0, L->rmx) + x->v + max(0, R->lmx);
	x->mx = max(x->mx, max(L->mx, R->mx));
}

inline void rotate(Node *x)
{
	Node *y = x->fa;
	int d = (x==lch(y));
	pushdown(y), pushdown(x);
	y->ch[!d] = x->ch[d];
	if (x->ch[d] != NIL) x->ch[d]->fa = y;
	x->fa = y->fa;
	if (y->fa != NIL)
		y->fa->ch[ y->fa->ch[1]==y ] = x;
	x->ch[d] = y;
	y->fa = x;
    pushup(y);
}

void splay(Node*&r, Node*x, Node*to=NIL)
{
	pushdown(x);
	for (Node *y, *z; x->fa != to; rotate(x)) {
		y = x->fa; z = y->fa;
		if (z != to) rotate((y==lch(z))^(x==lch(y)) ? x : y);
	}
	pushup(x);
	if (to == NIL) r = x;
}

Node* getkth(Node*&r, int k, Node*to=NIL)
{
	Node*t = r;
	pushdown(t);
	for (; k != lch(t)->sz + 1; pushdown(t))
		if (k <= lch(t)->sz) t = lch(t);
		else k -= lch(t)->sz + 1, t = rch(t);
	splay(r, t, to);
	return t;
}

void merge(Node*&x, Node*&y) //合并后的树保存在x中
{
	getkth(x, x->sz);
	getkth(y, 1);
	rch(x) = y; y->fa = x; pushup(x);
}

Node*build0(int l, int r, Node*fa) //建树并填值
{
	if (l > r) return NIL;
	int mid = (l+r)>>1;
	nds[mid].ch[0] = build0(l, mid-1, &nds[mid]);
	nds[mid].ch[1] = build0(mid+1, r, &nds[mid]);
	nds[mid].v = nds[mid].sum = arr[mid];
	nds[mid].fa = fa;
	pushup(&nds[mid]);
	return &nds[mid];
}

void build1(Node*t, int k) //将已建好的二叉树填上值
{
	if (t==NIL) return;
	build1(lch(t), k);
	build1(rch(t), k+1 + lch(t)->sz);
	t->v = arr[k + lch(t)->sz + 1];
	t->sam = t->rev = 0;
	pushup(t);
}

int qsum(int pos, int tot)
{
	getkth(r1, pos);
	getkth(r1, pos + tot + 1, r1);
	pushdown(rch(r1)->ch[0]);
	return rch(r1)->ch[0]->sum;
}

int qmax()
{
	int cnt = r1->sz;
	getkth(r1, 1), getkth(r1, cnt, r1);
	pushdown(rch(r1)->ch[0]);
	return rch(r1)->ch[0]->mx;
}

void insert(int pos, int tot)
{
	for (int i = 1; i<=tot; ++i) get(arr[i]);
	r2 = getkth(r2, tot + 1);
	Node*t = r2->ch[0];
	build1(t, 0);
	getkth(r1, pos+1);
	getkth(r1, pos+2, r1);
	r1->ch[1]->ch[0] = t;
	t->fa = rch(r1);
	r2->ch[0] = NIL;
	pushup(r2);
	pushup(rch(r1)); pushup(r1);
}

void erase(int pos, int tot)
{
	getkth(r1, pos);
	getkth(r1, pos+tot+1, r1);
	Node*t = rch(r1)->ch[0];
	rch(r1)->ch[0] = NIL;
	t->fa = NIL;
	merge(r2, t);
	pushup(rch(r1)); pushup(r1);
}

void makesame(int pos, int tot, int c)
{
	getkth(r1, pos);
	getkth(r1, pos+tot+1, r1);
	Node*&t = rch(r1)->ch[0];
	upsm(t, c);
	pushdown(rch(r1));
	pushup(rch(r1)); pushup(r1);
}

void reverse(int pos, int tot)
{
	getkth(r1, pos);
	getkth(r1, pos+tot+1, r1);
	uprv(lch(rch(r1)));
	pushdown(rch(r1));
	pushup(rch(r1)); pushup(r1);
}

int main()
{
	nil.ch[0] = nil.ch[1] = nil.fa = NIL;
	get(N), get(M);
	for (int i = 1; i<=N; ++i) get(arr[i]);
	r1 = build0(0, N+1, NIL);
	r2 = build0(N+2, MAXN-2, NIL);
	char op[99];
	int a, b, c;
	for (int i = 1; i<=M; ++i) {
		scanf("%s", op);
		if (op[0] == 'I')
			get(a), get(b), insert(a, b);
		else if (op[0] == 'D')
			get(a), get(b), erase(a, b);
		else if (op[2] == 'K')
			get(a), get(b), get(c), makesame(a, b, c);
		else if (op[0] == 'R')
			get(a), get(b), reverse(a, b);
		else if (op[0] == 'G')
			get(a), get(b), printf("%d\n", qsum(a, b));
		else printf("%d\n", qmax());
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值