数据结构模板集
并查集
有路径压缩和按秩合并两种。第二种稳定在\(\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;
}