题意:给定一个循环序列,支持以下操作:区间增加,区间翻转,单点插入/删除,移动光标,询问光标所指的位置的值。
首先一眼看出可以用线性数据结构(平衡树)来维护。然后这些操作都很基础。但是要注意光标怎么处理。可以维护一个k记录它指向第几个,但是这样在区间操作时候可能区间分别位于这个序列的两端(因为你破环为链了)。比较好的方式是每次人工使得光标指向的元素位于序列的最左端。
这种题以前都是写的splay,我之前也比较依赖这种数据结构。但是这种题目需要大量的删除、插入、拼接操作,splay由于维护了父亲指针,在做这种操作的时候需要修改的指针特别多。前几天专门学了非旋转式treap,由于这种treap的一切操作是基于split/merge的,所以几乎不用手动修改指针,所有指针的修改可以依赖这两个操作来完成。
理论上treap的常数应该接近avl和sbt这类自平衡二叉树,和splay完全不是一个数量级的,因为splay在旋转过程中需要修改大量的指针。但这道题上非选旋转式treap好像实际运行中优越性并不是很强。我猜想是这题数据范围比较小,指针修改比较少的原因,因为两个都只跑了400+ms。
这种treap的建树挺有意思的。随机化程度简直高仿。不过不知道一开始就像splay那样建成个满二叉树可不可以?这样需要手动给节点赋从下往上递增的伪随机值。
注意newnode的时候新建节点size为1!!!坑了我20多分钟!!!
#include<iostream> #include<cstdio> #include<cstring> #include<utility> #include<algorithm> #define rep(i,a,b) for(int i=a;i<=b;++i) #define erp(i,a,b) for(int i=a;i>=b;--i) #define LL long long #define fi first #define se second using namespace std; typedef pair<int, int> pii; const int MAXN = 200005; inline int ran() { static int sd = 1237; return sd = (sd*12371237)&0x7fffffff; } void get(int &r) { char c, f = 0; r = 0; do {c=getchar();if(c=='-')f=1;} while (c<'0'||c>'9'); do r=r*10+c-'0',c=getchar(); while (c>='0'&&c<='9'); if (f) r = -r; } int N, M, k1, k2; struct Treap { int lch[MAXN], rch[MAXN], sz[MAXN], fix[MAXN]; int val[MAXN], dt[MAXN]; bool rev[MAXN]; int ncnt, root; void clear() { ncnt = root = 0; fix[0] = -1; sz[0] = val[0] = dt[0] = 0; lch[0] = rch[0] = 0; } inline void uprev(int x) { if (x) swap(lch[x], rch[x]), rev[x] ^= 1; } inline void upadd(int x, int d) { if (x) val[x] += d, dt[x] += d; } inline void pushdown(int x) { if (!x) return; if (rev[x]) { uprev(lch[x]), uprev(rch[x]); rev[x] = 0; } if (dt[x]) { upadd(lch[x], dt[x]); upadd(rch[x], dt[x]); dt[x] = 0; } } inline void pushup(int x) { sz[x] = sz[lch[x]] + sz[rch[x]] + 1; } int NewNode(int i = 0) { ++ncnt; lch[ncnt] = rch[ncnt] = dt[ncnt] = 0; fix[ncnt] = ran(); val[ncnt] = i; rev[ncnt] = 0; sz[ncnt] = 1; return ncnt; } int merge(int a, int b) //大根堆 { if (!a || !b) return a|b; pushdown(a); pushdown(b); if (fix[a] > fix[b]) { rch[a] = merge(rch[a], b); return pushup(a), a; } else { lch[b] = merge(a, lch[b]); return pushup(b), b; } } pii split(int x, int k) { if (!x) return pii(0, 0); pushdown(x); pii y; if (sz[lch[x]] >= k) { y = split(lch[x], k); lch[x] = y.se, y.se = x; } else { y = split(rch[x], k - sz[lch[x]] - 1); rch[x] = y.fi, y.fi = x; } return pushup(x), y; } void del(int k) { pii x = split(root, k-1); pii y = split(x.se, 1); root = merge(x.fi, y.se); } int sta[MAXN]; int build(int *a, int n) { int x, las, p = 0; rep(i, 1, n) { x = NewNode(a[i]); las = 0; while (p && fix[sta[p]] < fix[x]) pushup(las = sta[p]), sta[p--] = 0; if (p) rch[sta[p]] = x; lch[x] = las; sta[++p] = x; } while (p) pushup(sta[p--]); return sta[1]; } void init(int *a, int n) { clear(); root = build(a, n); } void add(int d) { pii x = split(root, k2); upadd(x.fi, d); root = merge(x.fi, x.se); } void reverse() { pii x = split(root, k1); uprev(x.fi); root = merge(x.fi, x.se); } void ins(int t) { pii x = split(root, 1); int tmp = NewNode(t); root = merge(merge(x.fi, tmp), x.se); } void del() { root = split(root, 1).se; } void move(int dir) { int tot = sz[root]; pii x = split(root, dir==1 ? tot-1 : 1); root = merge(x.se, x.fi); } int quary() { pii x = split(root, 1); int ans = val[x.fi]; root = merge(x.fi, x.se); return ans; } } tp; int arr[MAXN]; int main() { int cid = 0, x; char op[99]; while (~scanf("%d%d%d%d", &N, &M, &k1, &k2)) { if (!(N|M|k1|k2)) break; printf("Case #%d:\n", ++cid); rep(i, 1, N) get(arr[i]); tp.init(arr, N); while (M --) { scanf("%s", op); switch(op[0]) { case 'a': get(x); tp.add(x); break; case 'r': tp.reverse(); break; case 'i': get(x); tp.ins(x); break; case 'd': tp.del(); break; case 'm': get(x); tp.move(x); break; default : printf("%d\n", tp.quary()); } } } return 0; }