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; }