BZOJ3506 BZOJ1552 排序机械臂 Splay区间翻转(数组版自底向上的写法)

首先做了这题才知道自己根本不会Splay, 虽然写过几个题目, 但是区间翻转标记下放没有仔细想过, 想想以前的区间翻转题目, 恰好没有考虑到我忽略的部分, 那就是标记下放的问题, 例如BZOJ3223每次操作会先找到区间两端, 注意到在找的过程中就会下放标记, 所以Splay(){}里面不需要下放标记, 但实际上如果直接知道了要把某个节点旋上来, 比如本题, 就要在Splay前先把当前节点到目标节点(或者到根)这条链上的标记都下放, 而且考虑到父亲的标记会影响儿子, 要从上到下放标记

本题相关:

直接用权值做编号

 

1.翻转L-->R这段只需将L-1旋到根, (R+1)旋到根的右儿子, 则L-->R的数全部在(R+1)的左儿子上了, 为了防止边界溢出(L为第一个数, 则(L-1)不存在, 或者R为末尾的数), 左右各加一个虚拟节点, 显然在题目给出的序列中每个数加上1是不影响答案的, 所以虚拟节点编号为1, n+2;

2.调出来交上去发现TLE了, 突然发现序列的数可以重复, 太恶心了, 那么离散化, 将中间的n个数从小到大编号为2-->n+1, 而且根据题意, 相同的数位置靠前的编号也靠前, 只要记录下位置排序时考虑到就行了, 最后按照位置再排一次序, 就得到了编号后的原序列, 编号各不相同;

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 100007; 
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}

//SSSSSSSppppppplllllllaaaaaaayyyyyyy
int n, root, sz, size[maxn], fa[maxn], c[maxn][2], val[maxn], rev[maxn];
int top, s[maxn];
struct seq {
	int v, id, pos;
} a[maxn];
bool cmp1(seq a, seq b) {
	return a.v < b.v || (a.v == b.v && a.pos < b.pos);
}
bool cmp2(seq a, seq b) {
	return a.pos < b.pos;
}
void update(int x) {
	size[x] = size[c[x][0]] + size[c[x][1]] + 1;
}
void push_down(int x) {
	if(rev[x]) {
		int l = c[x][0], r = c[x][1];
		swap(c[x][0], c[x][1]);
		rev[l] ^= 1; rev[r] ^= 1;
		rev[x] = 0;
	}
}
void rotate(int x, int &k) {
	int y = fa[x], z = fa[y], l, r;
	l = (c[y][0] == x) ? 0 : 1; r = l ^ 1; 
	if(y == k) k = x;
	else {
		if(c[z][0] == y) c[z][0] = x;
		else c[z][1] = x;
	}
	fa[x] = z; fa[y] = x; fa[c[x][r]] = y;
	c[y][l] = c[x][r]; c[x][r] = y;
	update(y); update(x);
} 
void splay(int x, int &k) {
	top = 0;
	for(int i = x; fa[i]; i = fa[i]) s[++top] = i;
	for(int i = top; i >= 1; i--) push_down(s[i]);
	//标记要从上到下传, 不然可能下面标记清空后又被上面传下来, 这样就不能处理完这条链上的标记 
	/*原来做BZOJ3223的时候由于每次操作需要先找到要操作的节点, 找的过程中就down了所有标记, 所以不用这一步, 
	结果做这题的时候到处考虑标记, 调了半天发现标记push_down顺序错了, 看来我根本不会Splay */ 
	while(x != k) {
		int y = fa[x], z = fa[y];
		if(y != k) {
			if((c[z][0] == y) ^ (c[y][0] == x)) rotate(x, k);
			else rotate(y, k);
		} rotate(x, k);
	}
} 

int find(int x, int rk) {
	if(!x) return 0;
	push_down(x);
	if(rk == size[c[x][0]] + 1) return x;
	else if(rk <= size[c[x][0]]) return find(c[x][0], rk);
	else return find(c[x][1], rk - size[c[x][0]] - 1); 
}

void build(int l, int r, int f) {
	if(l > r) return;
	int now = a[l].id, last = a[f].id;
	if(l == r) {
		fa[now] = last; size[now] = 1;
		if(l < f) c[last][0] = now;
		else c[last][1] = now;
	} else {
		int mid = (l + r) >> 1;
		now = a[mid].id;
		build(l, mid - 1, mid);
		build(mid + 1, r, mid);
		fa[now] = last; update(now);
		if(mid < f) c[last][0] = now;
		else c[last][1] = now;
	}
}
void solve(int k) {
	splay(k, root);
	printf("%d", size[c[root][0]]);
	if(k != n + 1) printf(" ");
	int x = find(root, k - 1), y = find(root, size[c[root][0]] + 2);
	splay(x, root); splay(y, c[x][1]);
	rev[c[y][0]] ^= 1;
}
//

int main() {
	n = read();
	//这个题最恶心的地方, 有重复编号, 需要离散化 
	for(int i = 1; i <= n; i++) a[i + 1].v = read(), a[i + 1].v++, a[i + 1].pos = i;
	//左右各添加一个虚拟节点, 相当于原题上所有编号都增加了1 
	sort(a + 2, a + n + 2, cmp1);
	for(int i = 2; i <= n + 1; i++) a[i].id = i;
	a[1].id = 1; a[n + 2].id = n + 2;
	sort(a + 2, a + n + 2, cmp2);
	build(1, n + 2, 0); root = a[(n + 3) >> 1].id;
	for(int i = 1; i <= n; i++) solve(i + 1);
	return 0;
}

  

posted @ 2016-01-28 10:08  using_namespace  阅读(886)  评论(0编辑  收藏  举报