|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
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 字典树处理抑或, dsu
- struct 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 做差 + 路径分类
|
|