数据结构模板集

并查集

  有路径压缩和按秩合并两种。第二种稳定在\(\mathcal O(\log n)\),可以可持久化。下面只放了带权并查集。

// 以NOI2001食物链为题
int fset(int x) {
	if (fa[x] == x) return x;
	int xx = fa[x];
	fa[x] = fset(xx);
	d[x] = (d[x]+d[xx])%3; // 权值一道压缩
	return fa[x];
}
void merge(int x, int y, int dis) { // x<-y dis
	if (fset(x) != fset(y))
		d[fa[y]] = (dis+d[x]-d[y]+3)%3, fa[fa[y]] = fa[x];
        // 先计算好d[fa[y]],再改父亲,否则有可能父亲是自身然后出错
}

树状数组

  小巧的数据结构,常数很小。下面是树状数组二分。

// 查询前缀刚好>=k的下标
int kth(int k) {
	int ans = 0, now = 0;
	per(i, 20, 0) {
		ans += 1<<i;
		if (ans > n || now + C[ans] >= k) ans -= 1<<i;
		else now += C[ans];
	}
	return ans+1;
}

线段树

  基本操作及可持久化等都略去。这里放线段树分裂和合并,注意分裂的复杂度是\(\mathcal O(\log n)\),而合并的复杂度是均摊的。

// 某些特殊情况下(就像这个代码),l和r都可以略去
void merge(int &x, int y, int l=1, int r=M) {
	if (!x || !y) { x |= y; return; }
	if (l == r) {
		sum[x] += sum[y];
		return;
	}
	int mid = l+r>>1;
	merge(lc[x], lc[y], l, mid); merge(rc[x], rc[y], mid+1, r);
	pushup(x);
}
// 分裂可以按下标/排名,和无旋treap很相像。这里将x分裂到y中
void split(int &x, int &y, int l, int r, int p) {
	if (!x) return;
	if (l >= p) { y = x, x = 0; return; }
	if (r < p) { y = 0; return; }
	int mid = l+r>>1;
	y = newnode();
	split(lc[x], lc[y], l, mid, p), split(rc[x], rc[y], mid+1, r, p);
	pushup(x), pushup(y);
}

ST表

  用处很大,离线\(\mathcal O(n\log n)/\mathcal O(1)\)查询区间最值。

void build_st(int *a, int n) {
    rep(i, 1, n) st[0][i] = a[i];
    for (int i = 1; 1<<i-1 < n; i++)
        for (int j = 1; j <= n-(1<<i-1); j++)
            st[i][j] = std::max(st[i-1][j], st[i-1][j+(1<<i-1)]);
}

int query(int s, int t) {
    int i = log2(t-s+1);
    return std::max(st[i][s], st[i][t-(1<<i)+1]);
}

倍增LCA

  \(\mathcal O(n\log n)/O(\log n)\)。有些很特殊的情况必须用这种方法求LCA。

// 预处理了倍增数组
int lca(int x, int y) {
	if (dep[x] < dep[y]) std::swap(x, y);
	for (int i = 0, j = dep[x]-dep[y]; j; i++, j >>= 1)
		if (j & 1) x = par[i][x];
	if (x == y) return x;
	per0(i, 18)
		if (par[i][x] != par[i][y]) x = par[i][x], y = par[i][y];
	return par[0][x];
}

树链剖分/长链剖分

(坑)

无旋Treap

  这个很好写,不容易挂。(博主痛苦地从指针版改写成了数组版)

const int maxn = 111111;

// fhq-treap core
int r[maxn], ch[maxn][2], sz[maxn], key[maxn], pt = 0;
int newnode(int x) {
	r[++pt] = rand(); sz[pt] = 1, key[pt] = x;
	return pt;
}
void pushup(int o) {
	sz[o] = sz[ch[o][0]]+sz[ch[o][1]]+1;
}
void split(int o, int x, int &l, int &r) { // before first num x
	if (!o) { l = r = 0; return; }
	if (key[o] < x) { l = o; split(ch[o][1], x, ch[l][1], r); pushup(l); }
	else { r = o; split(ch[o][0], x, l, ch[r][0]); pushup(r); }
}
int merge(int x, int y) {
	if (!x || !y) return x+y;
	return r[x] > r[y] ? (ch[x][1] = merge(ch[x][1], y), pushup(x), x) : (ch[y][0] = merge(x, ch[y][0]), pushup(y), y);
}
int kth(int o, int k) {
	for (;;) {
		int rk = sz[ch[o][0]]+1;
		if (k == rk) return key[o];
		k > rk ? (k -= rk, o = ch[o][1]) : o = ch[o][0];
	}
}
// fhq-treap operations
int rt;
void ins(int v) {
	int x, y; split(rt, v, x, y); rt = merge(x, merge(newnode(v), y));
}
void del(int v) {
	int x, y, z; split(rt, v, x, y), split(y, v+1, y, z), rt = merge(x, merge(ch[y][0], merge(ch[y][1], z)));
	// 注意结点删除时还要合并左右子树
}
int rank(int v) {
	int x, y, ans; split(rt, v, x, y); ans = sz[x]+1; rt = merge(x, y);
	return ans;
}
int pre(int v) {
	int x, y, ans; split(rt, v, x, y); ans = kth(x, sz[x]); rt = merge(x, y);
	return ans;
}
int suc(int v) {
	int x, y, ans; split(rt, v+1, x, y); ans = kth(y, 1); rt = merge(x, y);
	return ans;
}

int main() {
	for (int Q = read(); Q; Q--) {
		int opt = read(), x = read();
		if (opt == 1) ins(x);
		if (opt == 2) del(x);
		if (opt == 3) printf("%d\n", rank(x));
		if (opt == 4) printf("%d\n", kth(rt, x));
		if (opt == 5) printf("%d\n", pre(x));
		if (opt == 6) printf("%d\n", suc(x));
	}
	return 0;
}

Splay

  对序列操作性很强的数据结构,某些特性使得其能很好的与LCT搭配,以及其它很重要的特性对解题有帮助。

int ch[maxn][2], fa[maxn], sz[maxn], val[maxn], rev[maxn], tot = 0;
int newnode(int x) { sz[++tot] = 1, val[tot] = x; return tot; }
void pushup(int o) { sz[o] = sz[ch[o][0]] + sz[ch[o][1]] + 1; }
void pushdown(int o) { // 下传标记
	if (!rev[o]) return;
	std::swap(ch[o][0], ch[o][1]), rev[ch[o][0]] ^= 1, rev[ch[o][1]] ^= 1; rev[o] = 0;
}
int get(int o) { return ch[fa[o]][1] == o; } // 判断是左结点(0)还是右结点(1)
void rotate(int o) { // 旋转
	int f = fa[o], d = get(o);
	ch[fa[o] = fa[f]][get(f)] = o;
    fa[ch[f][d] = ch[o][!d]] = f;
    fa[ch[o][!d] = f] = o;
	pushup(f);
}
void splay(int o, int tp) { // 旋转到tp的下方(保证tp是o的祖先)
	static int st[maxn], top = 0;
	for (int x = o; x != tp; x = fa[x]) st[top++] = x;
	while (top) pushdown(st[--top]);
	for (; fa[o] != tp; rotate(o))
		if (fa[fa[o]] != tp) rotate(get(o) == get(fa[o]) ? fa[o] : o);
    pushup(o);
}

动态树(LCT)

  支持树连边、删边以及信息的维护。功能非常强大。

const int maxn = 111111;
// splay
int ch[maxn][2], val[maxn], sum[maxn], rev[maxn], fa[maxn], tot = 0;
int newnode(int x) {
    rev[++tot] = 0, val[tot] = sum[tot] = x;
    return tot;
}
void pushup(int o) {
    sum[o] = val[o] ^ sum[ch[o][0]] ^ sum[ch[o][1]];
}
void pushdown(int o) {
    if (!rev[o]) return;
    std::swap(ch[o][0], ch[o][1]); rev[ch[o][0]] ^= 1, rev[ch[o][1]] ^= 1; rev[o] = 0;
}
int get(int o) { return ch[fa[o]][1] == o ? 1 : (ch[fa[o]][0] == o ? 0 : -1); }
void rotate(int o) {
    int f = fa[o], d = get(o);
    fa[o] = fa[f]; if (~get(f)) ch[fa[f]][get(f)] = o;
    fa[ch[f][d] = ch[o][!d]] = f;
    fa[ch[o][!d] = f] = o;
    pushup(f);
}
void splay(int o) {
    static int st[maxn], top = 0, p;
    for (p = o; ~get(p); p = fa[p]) st[top++] = p;
    for (pushdown(p); top; pushdown(st[--top])) ;
    for (; ~get(o); rotate(o))
        if (~get(fa[o])) rotate(get(o) == get(fa[o]) ? fa[o] : o);
    pushup(o);
}
// lct
int a[maxn];
void init(int n) {
    rep(i, 1, n) newnode(a[i]);
}
void access(int x) {
    for (int y = 0; x; x = fa[y = x])
        splay(x), ch[x][1] = y, pushup(x);
}
void makeroot(int x) {
    access(x); splay(x); rev[x] ^= 1;
}
int findroot(int x) {
    access(x); splay(x);
    while (pushdown(x), ch[x][0]) x = ch[x][0];
    return splay(x), x;
}
void link(int x, int y) {
    if (makeroot(x), findroot(y) != x) fa[x] = y;
}
void cut(int x, int y) {
    if (makeroot(x), findroot(y) == x && fa[y] == x && !ch[y][0]) ch[x][1] = fa[y] = 0, pushup(x);
}
void modify(int x, int y) {
    splay(x); val[x] = y; pushup(x);
}
int query(int x, int y) {
    return makeroot(y), access(x), splay(x), sum[x];
}

替罪羊树

  很厉害的一种暴力数据结构,且复杂度正确。拍扁操作能够干一些其它平衡树干不了的东西。

// 注意替罪羊树由于使用了惰性删除,必须要结点合并
const int maxn = 111111;
const double alpha = 0.7;

int ch[maxn][2], val[maxn], sz[maxn], nd[maxn], valid[maxn], tot = 0, rub[maxn], cnt = 0;
#define lc ch[o][0]
#define rc ch[o][1]
int newnode(int x) {
    int o = cnt ? rub[--cnt] : ++tot;
    valid[o] = 0, sz[o] = nd[o] = 1, val[o] = x, lc = rc = 0;
    return o;
}
void del(int x) { rub[cnt++] = x; }
void pushup(int o) {
    sz[o] = sz[lc] + sz[rc] + valid[o];
    nd[o] = nd[lc] + nd[rc] + 1;
}
bool bad(int o) { return std::max(nd[lc], nd[rc]) > nd[o] * alpha; }
int *id, pool[maxn], pt;
void dfs(int o) {
    if (!o) return;
    dfs(lc);
    if (valid[o]) pool[++pt] = o;
    dfs(rc);
    if (!valid[o]) del(o);
}
int build(int l, int r) {
    if (l > r) return 0;
    int mid = l+r>>1, o = pool[mid];
    lc = build(l, mid-1), rc = build(mid+1, r);
    return pushup(o), o;
}
void rebuild(int &o) {
    pt = 0; dfs(o); o = build(1, pt);
}
void insert(int &o, int x) {
    if (!o) o = newnode(x);
    if (val[o] == x) valid[o]++;
    else insert(ch[o][x > val[o]], x);
    if (bad(o)) id = &o;
    pushup(o);
}
void remove(int o, int x) {
    if (val[o] == x) valid[o]--;
    else remove(ch[o][x > val[o]], x);
    pushup(o);
}
int rank(int o, int x) {
    int k = 1;
    for (; o; val[o] >= x ? o = lc : (k += sz[lc]+valid[o], o = rc)) ;
    return k;
}
int kth(int o, int k) {
    for (;;) {
        int rk = sz[lc];
        if (rk < k && k <= rk+valid[o]) return val[o];
        // 注意结点个数可能多个,需要判范围
        k <= rk ? o = lc : (k -= rk+valid[o], o = rc);
    }
}
int pre(int o, int x) {
    return kth(o, rank(o, x)-1);
}
int suc(int o, int x) {
    return kth(o, rank(o, x+1));
}

int rt;

int main() {
    for (int Q = read(); Q; Q--) {
        int opt = read(), x = read();
        if (opt == 1) insert(rt, x);
        if (opt == 2) remove(rt, x);
        if (opt == 3) printf("%d\n", rank(rt, x));
        if (opt == 4) printf("%d\n", kth(rt, x));
        if (opt == 5) printf("%d\n", pre(rt, x));
        if (opt == 6) printf("%d\n", suc(rt, x));
        if (id != NULL) rebuild(*id), id = NULL; // 重构
    }
    return 0;
}

左偏树

  一种特殊的堆,最主要的功能就是采用启发式合并,以\(\mathcal O(\log n)\)合并两个堆。

// 核心操作
int w[maxn], lc[maxn], rc[maxn], d[maxn];
int merge(int x, int y) {
	if (!x || !y) return x | y;
	if (w[x] > w[y]) std::swap(x, y);
	rc[x] = merge(rc[x], y);
	if (d[lc[x]] < d[rc[x]]) std::swap(lc[x], rc[x]);
	d[x] = d[rc[x]] + 1;
	return x;
}
posted @ 2020-06-05 12:08  AC-Evil  阅读(168)  评论(0编辑  收藏  举报