我好菜啊 Orz
数据结构
平衡树
代码对应 LOJ104 普通平衡树 。
Scapegoat Tree
#include <cstdio>
#include <ctype.h>
#include <algorithm>
using namespace std;
const int _N = 101000;
char *p1, *p2, buf[1 << 20];
inline char gc()
{
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin)) == p1 ? EOF : *p1++;
}
template<typename T>
void rd(T &num)
{
char tt;
bool flag;
while (!isdigit(tt = gc()) && tt != '-');
if (tt == '-')
num = 0, flag = 1;
else
num = tt - '0', flag = 0;
while (isdigit(tt = gc()))
num = num * 10 + tt - '0';
if (flag)
num = -num;
return;
}
namespace goat {
struct node {
node *l, *r;
int v, siz, cnt, exi;
} GT[_N], *Tl, *Rt, *Lun, *MPL[_N];
int MPL_cnt, Tmp_cnt, Tmp_v[_N], Tmp_exi[_N];
double Alpha = 0.6666;
void init()
{
Rt = Tl = Lun = GT;
Lun->l = Lun->r = Lun;
Lun->v = Lun->siz = Lun->cnt = Lun->exi = 0;
MPL_cnt = 0;
return;
}
void mpl_new(node *&p, int x, int exi)
{
p = MPL_cnt ? MPL[MPL_cnt--] : ++Tl;
p->l = p->r = Lun;
p->v = x;
p->cnt = 1;
p->siz = p->exi = exi;
return;
}
void rake(node *&p)
{
if (p == Lun)
return;
rake(p->l);
MPL[++MPL_cnt] = p;
if (p->exi)
Tmp_v[++Tmp_cnt] = p->v, Tmp_exi[Tmp_cnt] = p->exi;
rake(p->r);
return;
}
void maint(node *&p)
{
p->siz = p->l->siz + p->r->siz + p->exi;
p->cnt = p->l->cnt + p->r->cnt + 1;
return;
}
void build(node *&p, int l, int r)
{
if (l > r)
return;
int mid = l + r >> 1;
mpl_new(p, Tmp_v[mid], Tmp_exi[mid]);
build(p->l, l, mid - 1), build(p->r, mid + 1, r);
maint(p);
return;
}
void ins(node *&p, int x)
{
if (p == Lun) {
mpl_new(p, x, 1);
return;
}
if (p->v == x)
++p->exi;
else if (p->v > x)
ins(p->l, x);
else
ins(p->r, x);
if (p->l->cnt > Alpha * p->cnt || p->r->cnt > Alpha * p->cnt) {
Tmp_cnt = 0, rake(p);
build(p, 1, Tmp_cnt);
}
maint(p);
return;
}
void del(node *&p, int x)
{
if (p == Lun) {
return;
}
if (p->v == x && p->exi)
--p->exi;
else if (p->v > x)
del(p->l, x);
else
del(p->r, x);
maint(p);
return;
}
int get_rank(node *p, int x)
{
int t = 1;
while (p != Lun) {
if (p->v >= x)
p = p->l;
else
t += p->l->siz + p->exi, p = p->r;
}
return t;
}
int get_kth(node *p, int x)
{
while (p != Lun) {
if (p->l->siz < x && p->l->siz + p->exi >= x)
return p->v;
if (p->l->siz >= x)
p = p->l;
else
x -= p->l->siz + p->exi, p = p->r;
}
return -1;
}
}
using namespace goat;
int N;
int main()
{
rd(N);
init();
for (int i = 1; i <= N; ++i) {
int opt, x;
rd(opt), rd(x);
if (opt == 1)
ins(Rt, x);
else if (opt == 2)
del(Rt, x);
else if (opt == 3)
printf("%d
", get_rank(Rt, x));
else if (opt == 4)
printf("%d
", get_kth(Rt, x));
else if (opt == 5)
printf("%d
", get_kth(Rt, get_rank(Rt, x) - 1));
else if (opt == 6)
printf("%d
", get_kth(Rt, get_rank(Rt, x + 1)));
}
return 0;
}
Treap
Splay
FHQ Treap
Link-Cut-Tree
树套树
动态 DP
序列问题(比如区间最大连续和,历史最大值……)往往直接构造状态就可以了,不过用矩阵可能更好想。
树上问题的时候,矩阵才真正发挥作用、虽然有大佬可以观察状态,考虑修改对状态的影响,然后在树剖的线段树上二分最后影响位置,但这样思考太费劲了。直接用矩阵转移就可以了。
这类题代码量特别大,对我这种骨质疏松选手极不友善,所以必须确保头脑清醒再敲代码。
定义这种矩阵乘法:
发现满足结合律。(f) 表示子树最小代价, (g) 表示轻儿子的 (f) 之和, (v) 表示权值。
从儿子 (x) 向父亲 (y) 转移:
树剖,线段树。注意,一些转移的 (2 imes 2) 矩阵乘起来之后仍然只有两个有意义的元素,所以可以只记录这两个元素。而且这时候会发现,化成最简(大概是那个意思吧)的矩阵转移与一些非常绕的直接 DP 转移做法完全一致。
对于修改操作,只影响重链顶端节点的父亲的(也就是上一个重链底端) (g) 。无法直接改,需要计算当前链顶端修改后的增量,然后贡献给父亲。
点分治
-
找重心,这个过程是 (O(nlog n)) 的。
-
边分治很好,当需要在一堆里面匹配另一堆已有的数据时,因为一共只有两堆。不过边分治需要补成二叉树,否则菊花图就可以卡掉。 如果树并不能随便加点,那就不能边分了。还没有写过。
-
动态点分治也还没写。
prufer 序列
-
Cayley 公式
-
带编号、度数有限制的无根树计数。
卷积
-
多项式乘法原理, (C_k = sum A_xB_{k-x}) 类型的卷积
-
Dirichlet 卷积
容斥原理
- 一般的容斥
- min-max 容斥
矩阵
结合律
找随机的 (P) ,乘到两边来验证 (A cdot B = C) 是否成立。为什么有解(即出错)概率是数字范围分之一,我不懂啊!
((A cdot B)^n = A cdot (B cdot A)^{n-1} cdot B) 有时候会有点用。
多次询问求 (A^n) 的问题,预处理倍增数组之后可以把每次询问的 (O(k^3log n)) 降到 (O(k^2log n)) ,因为矩阵乘法变成了加法。
行列式
真的不懂啊,不过背下来吧。
多项式
- 乘
- 除
- 特征多项式
- 生成函数