|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
包含
树上dfs序 树上倍增 树链剖分 树上启发式合并
应该先判断是否连通...
树上问题先想低级算法 :
dfs栈 (把之前的祖先节点放进栈中, 双指针或者二分什么的)
树上差分 (点上加东西, 在 lca 和 lca 父节点各自减去一个)
dfs做差 (递归进入子树之前先记录当前的答案, dfs回来之后再记录当前的答案, 这样两个相减就是子树答案了)
树上倍增
树上问题的一些思考 :
lca单调性 (一个点是否是 lca 具有单调性 0000011111...)
善用 dfn 的有序性, 把无序变成有序, 把树上转换为序列
自己画图找性质
dfn 越近, lca深度越深
树上的函数 : lca, dist(点点距, 点链距), up(倍增向上跳), Insub(用 dfn 判断是否在子树中)
树上的路径分类: u 到 祖先, 祖先到 u
dep 就是 root 到 u 的 dis
一些题
B - LCA查询
给一个树, 每次询问给出两个集合 A, B, 让你求出在两个集合中各选一个点的 lca 深度最大是多少
可以二分深度, 对于每个 A 中的点向上跳这么多距离并且标记, 然后对于 B 也是往上跳, 如果能遇到标记说明这个 lca 深度是可行的 (lca单调性)
更简单的做法是利用 dfn, dfn 近的点 lca 深度越大, 所以排序 + 双指针while (m--) {
cin >> a;
for (int i = 1; i <= a; i++) cin >> A[i];
cin >> b;
for (int i = 1; i <= b; i++) cin >> B[i];
// 多测不清空, 亲人两行泪
A[a + 1] = B[b + 1] = 0;
sort(A + 1, A + a + 1, [](int x, int y) { return dfn[x] < dfn[y]; });
sort(B + 1, B + b + 1, [](int x, int y) { return dfn[x] < dfn[y]; });
int r = 0, ans = 0;
for (int i = 1; i <= a; i++) {
while (r + 1 <= b && dfn[B[r + 1]] < dfn[A[i]]) r++;
ans = max(ans, max(dep[lca(A[i], B[r])], dep[lca(A[i], B[r + 1])]));
}
cout << ans << '\n';
}
C - 动态造树
考虑把一个点连到树中, 可以知道应该是 dfn 相邻的两个点形成的路径到这个点的距离是最小的
所以用 set 维护加进来节点的 dfn , 每次查询和 x 相邻的两个点然后连边 (点链距)int Work(int u) {
if (st.empty())
return 0;
int a, b;
auto p = st.upper_bound(dfn[u]);
if (p == st.begin() || p == st.end()) {
a = rdfn[*st.begin()];
b = rdfn[*st.rbegin()];
} else {
a = rdfn[*p];
p--;
b = rdfn[*p];
}
// 点 u 到链 [a, b] 的距离
// if(dep[Lca(a, b)] > dep[Lca(a, u)]){
// u 在 lca(a, b) 上面的情况
// return (s[u] + s[Lca(a, b)] - 2*s[Lca(Lca(a, b), u)]);
// }
// else{
// u 在下面, 通过 dep 判断应该连谁的 lca
// if(dep[Lca(a, u)] < dep[Lca(b, u)]) swap(a, b);
// return (s[u] - s[Lca(a, u)]);
// }
// 下面这个是可以用路径长度推的
// ans = (dis(a, x) + dis(b, x) - dis(a, b)) / 2, 用 lca 展开即可
return (s[u] - s[Lca(a, u)] - s[Lca(b, u)] + s[Lca(a, b)]);
}
D - BZOJ3306 树
板子题, 只需要判断现在的根和询问的 x 子树关系即可
F - 树上交易
每个节点有一个商品的价格,可以买入或者卖出。
询问从一个节点到另一个节点,可以在路途中买入和卖出一次,求最大收益。
线段树维护区间 min, max(区间最大最小值), down, up(向上或者向下走的卖一次的收益)
G - Duff in the Army
树剖板子题, 也可以倍增, 每次归并两个 vector
接下来是几个 dsu on tree
总体步骤就是
1. 计算轻孩子, 删除
2. 计算重孩子
3. 把轻孩子的贡献加进来, 更新答案
4. 把 u 加进来, 更新答案
I - HDU6191
01 字典树处理抑或, dsustruct Trie {
int t[N * 30][2], s[N * 30], tot = 1, rt = 1;
void Insert(int x, int v) {
int p = rt, o;
for (int i = 30; s[p] += v, i >= 0; i--) {
o = (x >> i) & 1;
if (!t[p][o])
t[p][o] = ++tot;
p = t[p][o];
}
}
int Query(int x) {
int p = rt, o, res = 0;
for (int i = 30; i >= 0; i--) {
o = (x >> i) & 1;
if (s[t[p][o ^ 1]]) {
res |= (1 << i);
p = t[p][o ^ 1];
} else {
p = t[p][o];
}
}
return res;
}
} trie;
// QTREE
int dfn[N], rdfn[N], tot;
int siz[N], hc[N];
void dfs0(int u) {
dfn[u] = ++tot;
rdfn[tot] = u;
siz[u] = 1;
for (auto v : e[u]) {
dfs0(v);
siz[u] += siz[v];
if (siz[hc[u]] < siz[v])
hc[u] = v;
}
}
// DSU on tree
void Modify(int x, int v) {
for (int i = dfn[x]; i < dfn[x] + siz[x]; i++) {
trie.Insert(val[rdfn[i]], v);
}
}
void Solve(int u) {
for (auto v : e[u]) {
if (v == hc[u])
continue;
Solve(v);
Modify(v, -1);
}
if (hc[u])
Solve(hc[u]);
for (auto v : e[u]) {
if (v == hc[u])
continue;
Modify(v, 1);
}
trie.Insert(val[u], 1);
for (auto x : ask[u]) {
ans[x.second] = trie.Query(x.first);
}
}
J - Race
板子题void Solve(int u, int f) {
for (auto y : e[u]) {
if (y.v == f || y.v == hc[u])
continue;
Solve(y.v, u);
mp.clear();
}
if (hc[u])
Solve(hc[u], u);
for (auto y : e[u]) {
if (y.v == f || y.v == hc[u])
continue;
for (int i = dfn[y.v]; i < dfn[y.v] + siz[y.v]; i++) {
Checkmin(rdfn[i], u);
}
for (int i = dfn[y.v]; i < dfn[y.v] + siz[y.v]; i++) {
Add(rdfn[i]);
}
}
Checkmin(u, u);
Add(u);
}
K - GSS7
树剖 + 线段树维护最大字段和板子题
L - 天天爱跑步
树上差分 + dfs 做差 + 路径分类
|
|