一条链上的树链个数的数量级是 ,所以 34 号操作的复杂度就是 。
好像比暴力还慢……
辣鸡算法,白讲了
这是因为划分树链还有讲究,一个优秀的树链划分方法(其实就是 dfs 顺序)能将一条路径上的树链个数的数量级控制在 .
这种树链划分方法称为 重链剖分
重儿子:对于非叶节点 ,它的重儿子 是 的所有儿子中子树大小最大的。
重边:对于非叶节点 ,设它的重儿子为 ,边 称为一条重边。
重链:重边首尾衔接形成的链称为重链。
轻儿子:对于非叶节点 ,它的轻儿子是除重儿子之外的其他所有儿子。
轻边:对于非叶节点 ,设它的一个轻儿子为 ,边 称为一条轻边。
链头:对于一条重链,深度最小的节点称为链头。
红字是子树大小,蓝点是重儿子,白点是轻儿子,黄色标记出的边是重边,绿色圈圈出的是重链。
注:落单的结点也当作重链。
证明:
所以把这颗树的重链作为树链,34 操作的复杂度是 ,可以接受。
重链剖分要做的事就是通过 轻重边剖分 把树分成许多条链,对每个节点用 dfs 序重新编号,把树上问题转化为序列问题,以套用其他链状数据结构(如线段树等)来维护,这样就可以非常方便的处理同一条链上的若干点/边。
重链剖分可以做的事情有:
如图所示,红字就是 dfs 序。
使用这种方法,就可以完成 1234 四种操作了。
(LuoguP3384 【模板】轻重链剖分)
void dfs1(int x, int fa) {
f[x] = fa, d[x] = d[fa] + 1, siz[x] = 1;
// 求子树大小
int maxSiz = 0, maxNum = 0;
for (auto q : head[x]) {
if (q != fa) {
dfs1(q, x);
siz[x] += siz[q];
if (siz[q] > maxSiz) maxSiz = siz[q], maxNum = q;
}
} // 遍历儿子,找出重儿子
son[x] = maxNum; // 求出重儿子
}
(LuoguP3384 【模板】轻重链剖分)
int dfncnt = 1;
void dfs2(int x, int t) {
rk[dfncnt] = x, dfn[x] = dfncnt++, top[x] = t;
// 记录 dfs 序
if (son[x]) dfs2(son[x], t); // 先访问重儿子
for (auto q : head[x]) {
if (q != f[x] && q != son[x]) {
dfs2(q, q);
}
} // 再访问轻儿子
}
(LuoguP3384 【模板】轻重链剖分)
void modify1(int x, int y, int z) { // 路径修改
while (top[x] != top[y]) { // 不在一条链上就跳链
if (d[top[x]] < d[top[y]]) swap(x, y); // 跳深度小的
t[1].modify(dfn[top[x]], dfn[x], z); // 用线段树修改
x = f[top[x]]; // 跳
}
if (d[x] < d[y]) swap(x, y);
t[1].modify(dfn[y], dfn[x], z); // 跳到一条链上,修改 (x,y) 段
}
(LuoguP3384 【模板】轻重链剖分)
int query1(int x, int y) { // 路径查询
int ans = 0;
while (top[x] != top[y]) { // 不在一条链上就跳链
if (d[top[x]] < d[top[y]]) swap(x, y);
ans = (ans + t[1].query(dfn[top[x]], dfn[x])) % mod; // 查询
x = f[top[x]]; // 跳
}
if (d[x] < d[y]) swap(x, y);
ans = (ans + t[1].query(dfn[y], dfn[x])) % mod; // 跳到一条链上,查询 (x,y) 段
return ans;
}
(LuoguP3384 【模板】轻重链剖分)
void modify2(int x, int z) { t[1].modify(dfn[x], dfn[x] + siz[x] - 1, z); }
// 修改子树
int query2(int x) { return t[1].query(dfn[x], dfn[x] + siz[x] - 1); }
// 查询子树
int LCA (int x, int y) {
while (top[x] != top[y]) {
if (d[top[x]] < d[top[y]]) swap(x, y);
x = fa[top[x]];
}
if (d[x] < d[y]) return x;
else return y;
}
复杂度为 ,且常数较小。
裸题
每次安装软件,就把根节点到 软件路径上的值全部变为 1
同理,每次卸载软件,就把 以及它的子树的值变为 0
线段树维护即可
参考资料: