「学习总结」数据结构

真的好爱 Splay!

好像只有 LCT 中才有必要写 Splay 吧…… 其实 非旋 Treap 真的是又短,有小,又快……

数据结构

平衡树

Splay

  • 删除结点时,不是 Splay 到根然后合并两边的子树,而是将待删除的结点前驱 Splay 到根节点, 后继 Splay 到根节点右儿子,那么待删除的结点就在 ls(rs(rt)) 上。这样可以删除集合内一个区间的元素,也方便后面维护序列。
    void erase(int v){
    	PII pre = _pred(v), nxt = _nxtd(v);
    	splay(pre.se); splay(nxt.se, rt);
    	int &target = ls(rs(rt)); cnt[target] --; si[target]--;
    	if(!cnt[target]) target = 0;
    	maintain(rs(rt)); maintain(ls(rt));  maintain(rt);
    }
  • 如果空间要求较严格,可以考虑手写一个小的内存管理函数,删除时回收内存,新增元素时优先使用之前删除的结点内存。
stack<int> Mem;
int make(){
	int t; int o = (Mem.empty() ? ++tot : (t = Mem.top(), Mem.pop(), t));
	ch[o][1] = ch[o][0] = v[o] = fa[o] = 0;
	si[o] = 1;
	return o;
}
void recycle(int o = -1) { if(o == -1) o = rt; if(!o) return ; Mem.push(o); recycle(ls(o)); recycle(rs(o)); }
  • 如果需要开多棵 Splay,可以考虑只封装每棵 Splay 的根节点,对于结点内存的申请和释放统一管理
  • 一个很方便的调试 Splay 的函数:
    void dfs(int o = -1, int indent = 0){
    	if(o == -1) o = rt;
    	if(!o)  { cerr << string(indent, ' ') << "# null" << endl; return ; }
    	push(o);
    	assert(!rs(o) || fa[rs(o)] == o); dfs(rs(o), indent + 10);
    	cerr << string(indent, ' ') << "# v=" << val[o] << " ans=" << v[o].ans << endl;
    	assert(!ls(o) || fa[ls(o)] == o); dfs(ls(o), indent + 10);
    }
  • 建立哨兵结点作为整个 Splay 的最小最大值是一个通用技巧,删除操作依赖于前驱和后继结点,所以哨兵结点时必要的。维护序列时注意消除哨兵结点对答案的影响。

维护集合

一种动态维护集合元素,查询集合元素信息的解决方案。 - 为了优化常数和简化代码,查询前驱后继不再采用 插入-删除 式查询。查询元素排名之后直接选取前驱和后继即可。

PII _pred(int v){
	PII res = rank(rt, v);
	return select(rt, res.fi - 1);
} int pred(int v) { PII res = _pred(v); splay(res.se); return res.fi; }
- rankselect 返回值为 pair<int, int> 其中,前一个指答案,后一个是作用结点,方便后期获取结点信息(Splay)。 int rank(int v) { PII res = rank(rt, v); if(res.se) splay(res.se); return res.fi - 1; }

普通平衡树

struct Splay_t{
	int ch[_][2], cnt[_], si[_], v[_], fa[_], tot, rt;
	typedef pair<int, int> PII;
	#define ls(o) (ch[o][0])
	#define rs(o) (ch[o][1])
	#define maintain(o) (si[o] = si[ls(o)] + cnt[o] + si[rs(o)])
	#define get(o) (o == ch[fa[o]][1])
	int make(int f, int V){
		tot++;
		ch[tot][1] = ch[tot][0] = 0;
		si[tot] = cnt[tot] = 1;
		fa[tot] = f; v[tot] = V;
		if(f) ch[f][v[f] < V] = tot;
		return tot;
	}
	void rotate(int o){
		int p = fa[o], gp = fa[fa[o]], chk = get(o);
		ch[p][chk] = ch[o][chk^1]; fa[ch[o][chk^1]] = p;
		ch[o][chk^1] = p; fa[p] = o;
		fa[o] = gp; if(gp) ch[gp][ch[gp][1] == p] = o;
		maintain(p); maintain(o);
	}
	void splay(int o, int tgp = 0){
		for(int f = fa[o]; f = fa[o], f != tgp; rotate(o)) if(fa[f] != tgp) rotate(get(o) == get(f) ? f : o);
		if(!tgp) rt = o;
	}
	int ins(int o, int f, int V){
		if(!o) return make(f, V);
		if(v[o] == V) return (cnt[o]++, si[o]++, o);
		int r; return (r = ins(V < v[o] ? ls(o) : rs(o), o, V), maintain(o), r);
	} void ins(int v) { int t = ins(rt, 0, v); splay(t); }

	PII rank(int o, int V){
		if(!o) return mp(1, 0);
		if(V == v[o]) return mp(si[ls(o)] + 1, o);
		PII res;
		if(V < v[o]) return rank(ls(o), V);
		else return (res = rank(rs(o), V), res.fi += cnt[o] + si[ls(o)], res);
	} int rank(int v) { PII res = rank(rt, v); if(res.se) splay(res.se); return res.fi - 1; }

	PII select(int o, int k){
		if(k <= si[ls(o)]) return select(ls(o), k);
		else {
			if(k <= si[ls(o)] + cnt[o]) return mp(v[o], o);
			else return select(rs(o), k - cnt[o] - si[ls(o)]);
		}
	} int select(int k) { PII res = select(rt, k + 1); splay(res.se); return res.fi; }

	PII _pred(int v){
		PII res = rank(rt, v);
		return select(rt, res.fi - 1);
	} int pred(int v) { PII res = _pred(v); splay(res.se); return res.fi; }

	PII _nxtd(int v){
		PII res = rank(rt, v);
		return select(rt, res.fi + cnt[res.se]);
	} int nxtd(int v) { PII res = _nxtd(v); splay(res.se); return res.fi; }

	void erase(int v){
		PII pre = _pred(v), nxt = _nxtd(v);
		splay(pre.se); splay(nxt.se, rt);
		int &target = ls(rs(rt)); cnt[target] --; si[target]--;
		if(!cnt[target]) target = 0;
		maintain(rs(rt)); maintain(ls(rt));  maintain(rt);
	}
	Splay_t(){ tot = rt = 0; ins(INT_MAX); ins(INT_MIN); }
};

维护序列

一种支持增删元素的,线段树替代方案。 本质上和线段的思想很相似,一样采用信息合并和懒标记优化复杂度,每个结点也和线段树一样存储一个子区间的信息。 唯一不同的就是线段树是一种 Leafy Tree,即所有信息都只存储在叶子结点上,其结构导致无法删除和新增元素。 - 注意懒标记和线段树的定义是一样的,其作用节点的信息首先被修改。类比线段树的懒标记直接写就好了。 - 平衡树合并信息和线段树合并信息不同点在于:线段树是合并两边的信息,平衡树是先合并左子结点信息和中间结点信息再与有子结点合并,其实还是因为线段树是一种 Leafy Tree —— 和平衡树有本质区别。 - 因为 Splay 可以相对较好的控制树的形态,可以让一个子树组成任意区间,所以能够很好的维护序列。 - 核心操作:

void Make_Range(int L, int R){
	int pre = find(rt, L - 1), suf = find(rt, R + 1);
	splay(pre); splay(suf, rt);
}

「NOI2005」维护数列

const int _ = 5e5 + 10;
struct Splay_t{
	stack<int> Mem;
    #define none (-30000)
    struct data_t{
        int sum, per, suf, ans;
        data_t(){ per = suf = ans = -1e9; sum = 0; };
        data_t(int V) { sum = per = suf = ans = V; }
        void ret(int v, int len){
            if(v <= 0) suf = per = ans = v, sum = v * len; // changed `sum = v` to `sum = v * len`.
            else       sum = suf = per = ans = v * len;
        }
        void rev() { swap(per, suf); }
        data_t operator + (const data_t & B) {
	        data_t A = *this, res;
	        res.sum = A.sum + B.sum;
	        res.per = max(A.per, A.sum + B.per);
	        res.suf = max(A.suf + B.sum, B.suf);
	        res.ans = max(max(A.ans, B.ans), A.suf + B.per);
	        return res;
	    }
    };

    int ch[_][2], fa[_], si[_], rt, tot; data_t v[_];
    short val[_];
	bool tag_rev[_]; short tag_ret[_];
    #define ls(o) (ch[o][0])
    #define rs(o) (ch[o][1])
    #define maintain(o) (si[o] = si[ls(o)] + si[rs(o)] + 1, v[o] = v[ls(o)] + data_t(val[o]) + v[rs(o)])
    #define get(o) (ch[fa[o]][1] == o)
    int make(){
    	if(!Mem.empty()){
    		int o = Mem.top(); Mem.pop();
    		tag_ret[o] = none;
			tag_rev[o] = false;
			ch[o][1] = ch[o][0] = si[o] = val[o] = fa[o] = 0;
			v[o] = data_t();
			return o;
		} else {
			tot++;
			tag_ret[tot] = none;
			tag_rev[tot] = false;
			ch[tot][1] = ch[tot][0] = si[tot] = val[tot] = fa[tot] = 0;
			v[tot] = data_t();
			return tot;
		}

	}
	void recycle(int o){ if(!o) return ; Mem.push(o); recycle(ls(o)); recycle(rs(o)); }

    Splay_t() { rt = tot = 0; }
    void tar_rev(int o){
        v[o].rev();
        swap(ls(o), rs(o));
        tag_rev[o] ^= 1;
    }
    void tar_ret(int o, int V){
        v[o].ret(V, si[o]);
		val[o] = V;
        tag_ret[o] = V;
    }
    void push(int o){
        if(tag_ret[o] != none){
            if(ls(o)) tar_ret(ls(o), tag_ret[o]);
            if(rs(o)) tar_ret(rs(o), tag_ret[o]);
            tag_ret[o] = none;
        }
        if(tag_rev[o]){
            if(ls(o))tar_rev(ls(o));
            if(rs(o))tar_rev(rs(o));
            tag_rev[o] = 0;
        }
    }
    void rotate(int o){
        int p = fa[o], gp = fa[fa[o]], chk = get(o);
        ch[p][chk] = ch[o][chk ^ 1]; fa[ch[o][chk ^ 1]] = p;
        ch[o][chk ^ 1] = p; fa[p] = o;
        fa[o] = gp; if(gp) ch[gp][ch[gp][1] == p] = o;
        maintain(p); maintain(o);
    }
    void splay(int o, int tgp = 0){
        for(int f = fa[o]; f = fa[o], f != tgp; rotate(o)){
            push(f); push(o);
            if(fa[f] != tgp) rotate(get(o) == get(f) ? f : o);
        }
        if(!tgp) rt = o; // changed `rt = tgp`  to `rt = o`.
    }
    void build_sub(int &o, int f, int L, int R, int *A){
        if(L > R) return ;
        int mid = (L + R) >> 1;
        o = make(); fa[o] = f; val[o] = A[mid]; v[o] = data_t(A[mid]); si[o] = 1;
        if(L == R) return ;
        if(L <= mid - 1) build_sub(ls(o), o, L, mid - 1, A);
        if(mid + 1 <= R) build_sub(rs(o), o, mid + 1, R, A);
        maintain(o);
    }
	void build(int L, int R, int *A){
		build_sub(rt, 0, L, R, A);
	}

    int insert(int &o, int f, int target, int *A, int len){
        if(!o) return build_sub(o, f, 1, len, A), o;
        int r = 0; push(o);
        if(target <= si[ls(o)]) r = insert(ls(o), o, target, A, len);
        else r = insert(rs(o), o, target - si[ls(o)] - 1, A, len);
        maintain(o); return r;
    } void insert(int *A, int pos, int len) { int r = insert(rt, 0, pos, A, len); splay(r); }

    int find(int o, int k){
        push(o);
        if(k <= si[ls(o)]) return find(ls(o), k);
        else {
            if(k <= si[ls(o)] + 1) return o;
            else return find(rs(o), k - 1 - si[ls(o)]);
        }
    }
    void Make_Range(int L, int R){
        int per = find(rt, L - 1), suf = find(rt, R + 1);
        splay(per); splay(suf, rt);
    }
    void erase(int L, int R){
        Make_Range(L, R);
        int & target = ls(rs(rt));
        recycle(target); target = 0; maintain(rs(rt)); maintain(rt);
    }
    void rev(int L, int R){
        Make_Range(L, R);
        int & target = ls(rs(rt));
        tar_rev(target); maintain(rs(rt)); maintain(rt);
    }
    void ret(int L, int R, int V){
        Make_Range(L, R);
        int & target = ls(rs(rt));
        tar_ret(target, V); maintain(rs(rt)); maintain(rt);
    }
    int query_sum(int L, int R){
        Make_Range(L, R);
        int & target = ls(rs(rt));
        return v[target].sum;
    }
    int query_ans(){
        return v[rt].ans;
    }
   void dfs(int o = -1, int indent = 0){
   	if(o == -1) o = rt;
   	if(!o)  { cerr << string(indent, ' ') << "# null" << endl; return ; }
   	push(o);
   	if(rs(o)) assert(fa[rs(o)] == o); dfs(rs(o), indent + 10);
   	cerr << string(indent, ' ') << "# v=" << val[o] << " ans=" << v[o].ans << endl;
   	if(ls(o)) assert(fa[ls(o)] == o); dfs(ls(o), indent + 10);
	}
};
Splay_t t;
int A[_], n, m;
char opt[100];
int main(){ //freopen(".in", "r", stdin); freopen(".out", "w", stdout);
    Read(n)(m); n += 2; rep(i, 2, n - 1) Read(A[i]);
	A[1] = A[n] = none; // added line `A[1] = A[n] = -inf`.
    t.build(1, n, A);
    rep(tt, 1, m){
        scanf("%s", opt); char sign0 = opt[0], sign1 = opt[3];
        if(sign0 == 'I') { // Insert a sequence.
            int pos, tot; Read(pos)(tot); rep(i, 1, tot) Read(A[i]); pos++;
            t.insert(A, pos, tot);
        } else if(sign0 == 'D'){ // Delete a sequence.
            int pos, tot; Read(pos)(tot); pos++;
            t.erase(pos, pos + tot - 1);
        } else if(sign0 == 'R'){ // Reverse a sequence.
            int pos, tot; Read(pos)(tot); pos++;
            t.rev(pos, tot + pos - 1);
        } else if(sign0 == 'G'){ // calc the sum of sequence.
            int pos, tot; Read(pos)(tot); pos++;
            printf("%d\n", t.query_sum(pos, pos + tot - 1));
        } else if(sign1 == '-'){ // calc the max sum sub sequence.
            printf("%d\n", t.query_ans());
        } else { // make same on the sequence.
            int pos, tot, c; Read(pos)(tot)(c); pos++;
            t.ret(pos, pos + tot - 1, c);
        }
    }
    return 0;
}

非旋 Treap

udp:2021/01/02. u1s1,这东西又短 又小 又快。splay 可能真的只有 LCT 里面才出场了。

int rand(){
	static int seed = 1020031005;
	return seed = ((seed +0ll+ 20031006) % 998244353);
}
namespace FHQ{
    const int _ = 1e5 + 100;
    int ch[_][2], si[_], v[_], tot = 0, rt = 0;
    #define ls(o) (ch[o][0])
    #define rs(o) (ch[o][1])
    #define maintain(o) (void)(si[o] = si[ls(o)] + si[rs(o)] + 1)
    #define make(vs) (++tot, v[tot] = vs, si[tot] = 1, ch[tot][0] = ch[tot][1] = 0, tot)
    #define mg(a, b, c) (merge(merge(a, b), c))
    void split(int o, int V, int &x, int &y){
        if(!o) return (void)(x = y = 0);
        if(v[o] <= V) return x = o, split(rs(o), V, rs(o), y), maintain(o);
        return y = o, split(ls(o), V, x, ls(o)), maintain(o);
    }
    int merge(int x, int y){
        if(x == 0 || y == 0) return x + y;
        if(rand() % (si[x] + si[y]) + 1 <= si[x]) return rs(x) = merge(rs(x), y), maintain(x), x;
        else return ls(y) = merge(x, ls(y)), maintain(y), y;
    }
    void ins(int x){ int t0, t1 = make(x), t2; split(rt, x, t0, t2); rt = mg(t0, t1, t2); }
    void erase(int V){ int x, y, z; split(rt, V, y, z); split(y, V - 1, x, y); y = merge(ls(y), rs(y)); rt = mg(x, y, z); }
    int rank(int V) { int x, y; split(rt, V - 1, x, y); int res = si[x]; rt = merge(x, y); return res + 1; }
    int select(int V, int o = -1){ if(o == -1) o = rt; if(V <= si[ls(o)]) return select(V, ls(o)); else { if(V <= si[ls(o)] + 1) return v[o]; else return select(V - si[ls(o)] - 1, rs(o)); } }
    int pred(int V){ int x, y; split(rt, V - 1, x, y); int res = select(si[x], x); rt = merge(x, y); return res; }
    int succ(int V){ int x, y; split(rt, V, x, y); int res = select(1, y); rt = merge(x, y); return res; }
} using FHQ:: ins ;using FHQ:: erase ;using FHQ:: select ;using FHQ:: rank ;using FHQ:: pred ;using FHQ:: succ ;