欧拉序动态维护树直径
Posted mrzdtz220
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了欧拉序动态维护树直径相关的知识,希望对你有一定的参考价值。
https://zhuanlan.zhihu.com/p/84236967
https://www.cnblogs.com/TinyWong/p/11260601.html
一个月过去了,我还是没有学动态点分治...
欧拉序保存了每个节点进入和返回的情况,$n$ 个结点的树,欧拉序列长度为 $2n - 1$。
两个结点的LCA就是它们在欧拉序中出现的位置的区间中,深度最小的那个结点。
对于边权为正的树,上面定义中的深度也可以换成到根的距离,用dis[u]表示结点 $u$ 到根的距离。
那么两个节点的距离就可以表示成 $dis[u] + dis[v] - 2 * dis[lca]$
设两个结点在欧拉序中出现的位置分别为 $p$,$q$,不要求第一次出现。
那么也就是 $dis[dfn[p]] + dis[dfn[q]] - 2 * min(dis[x])$,$x in [p, q]$
对于修改一个边权,就是区间修改。这没啥好说的。
对于查询直径,一棵树的直径就是 $$ max_{1 leq a leq c leq b leq 2n - 1}{dis[a] + dis[b] - 2 * dis[c]} $$。
这个东西就相当于三个区间的合并,可以变成两个区间合并。
一个数组val存储dis值,一个数组m存储-2*dis值,一个数组lm存储该区间里的max{val}+ max{m},其中val是左儿子的,m是右儿子的。一个数组mr存储该区间里的max{m}+max{val},其中m是左儿子的,val是右儿子的。
一个数组lmr存储区间的直径,等于max{lm[lp] + val[rp],val[lp] + mr[rp]}
记录直径端点就再开几个数组存一下就好了。
CEOI 2019 day 1 online mirror (unrated, IOI format) B - Dynamic Diameter
#include <bits/stdc++.h> #define ll long long namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = ‘ ‘; int p, p3 = -1; void read() {} void print() {} inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } inline void flush() { fwrite(buf2, 1, p3 + 1, stdout), p3 = -1; } template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == ‘-‘) f = -1; ch = getc(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); } x *= f; read(oth...); } template <typename T, typename... T2> inline void print(T x, T2... oth) { if (p3 > 1 << 20) flush(); if (x < 0) buf2[++p3] = 45, x = -x; do { a[++p] = x % 10 + 48; } while (x /= 10); do { buf2[++p3] = a[p]; } while (--p); buf2[++p3] = hh; print(oth...); } } // using namespace IO #define read IO::read #define print IO::print const int N = 2e5 + 7; int dfn[N], tol, fa[N], in[N], out[N]; ll dis[N], weight[N]; #define pii pair<ll, int> #define fi first #define se second std::vector<std::pii> G[N]; void dfs(int u, int pre) { dfn[in[u] = ++tol] = u; for (auto p: G[u]) { int v = p.se; if (v == pre) continue; fa[v] = u; dis[v] = dis[u] + p.fi; weight[v] = p.fi; dfs(v, u); dfn[++tol] = u; } out[u] = tol; } struct E { int u, v; } e[N]; struct Seg { #define lp p << 1 #define rp p << 1 | 1 static const int NN = N * 4; ll val[NN], m[NN], lm[NN], mr[NN], lmr[NN], lazy[NN]; void pushup(int p) { val[p] = std::max(val[lp], val[rp]); m[p] = std::max(m[lp], m[rp]); lm[p] = std::max(std::max(lm[lp], lm[rp]), val[lp] + m[rp]); mr[p] = std::max(std::max(mr[lp], mr[rp]), m[lp] + val[rp]); lmr[p] = std::max(std::max(lmr[lp], lmr[rp]), std::max(val[lp] + mr[rp], lm[lp] + val[rp])); } void build(int p, int l, int r) { if (l == r) { int index = dfn[l]; val[p] = dis[index]; lazy[p] = 0; m[p] = -2 * dis[index]; lm[p] = mr[p] = -dis[index]; lmr[p] = 0; return; } int mid = l + r >> 1; build(lp, l, mid); build(rp, mid + 1, r); pushup(p); } void add(int p, ll w) { val[p] += w; lazy[p] += w; lm[p] -= w; mr[p] -= w; m[p] -= 2 * w; } void pushdown(int p) { if (lazy[p]) { add(lp, lazy[p]); add(rp, lazy[p]); lazy[p] = 0; } } void update(int p, int l, int r, int x, int y, ll w) { if (x <= l && y >= r) { add(p, w); return; } pushdown(p); int mid = l + r >> 1; if (x <= mid) update(lp, l, mid, x, y, w); if (y > mid) update(rp, mid + 1, r, x, y, w); pushup(p); } } seg; int main() { // freopen("in.txt", "r", stdin); int n, q; ll mod; read(n, q, mod); for (int i = 1; i < n; i++) { int u, v; ll c; read(u, v, c); G[u].push_back(std::pii(c, v)); G[v].push_back(std::pii(c, u)); e[i] = {u, v}; } dfs(1, 0); seg.build(1, 1, tol); //print(tol); ll ans = 0; while (q--) { int d; ll w; read(d, w); d = (d + ans) % (n - 1) + 1; w = (w + ans) % mod; int u = (fa[e[d].u] == e[d].v) ? e[d].u : e[d].v; seg.update(1, 1, tol, in[u], out[u], w - weight[u]); weight[u] = w; print(ans = seg.lmr[1]); } IO::flush(); return 0; }
The Preliminary Contest for ICPC Asia Shanghai 2019 A - Lightning Routing I
一个点到其他任意端点的最远距离肯定是直径的端点之一。那就查询一下当前结点和两个直径端点的距离,取个max就好了。
#include <bits/stdc++.h> namespace IO { char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = ‘ ‘; int p, p3 = -1; void read() {} template <typename T, typename... T2> inline void read(T &x, T2 &... oth) { T f = 1; x = 0; char ch = getchar(); while (!isdigit(ch)) { if (ch == ‘-‘) f = -1; ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getchar(); } x *= f; read(oth...); } } // using namespace IO #define read IO::read #define ll long long #define pii pair<ll, int> #define fi first #define se second const int N = 2e5 + 7; template<class T> inline T max(T a, T b) { return a > b ? a : b; } template<class T> inline T min(T a, T b) { return a < b ? a : b; } struct Edge { int u, v; } edge[N]; int n, dep[N], fa[N], dfn[N], tol, in[N], out[N]; int lg[N], st[N][24]; ll dis[N], weight[N]; std::vector<std::pii> G[N]; struct Seg { #define lp p << 1 #define rp p << 1 | 1 static const int SIZE = 4 * N; ll val[SIZE], m[SIZE], lm[SIZE], mr[SIZE], lmr[SIZE], lazy[SIZE]; int val_node[SIZE], lm_node[SIZE], mr_node[SIZE], node1[SIZE], node2[SIZE]; void pushup(int p) { val[p] = max(val[lp], val[rp]); if (val[p] == val[lp]) val_node[p] = val_node[lp]; else val_node[p] = val_node[rp]; m[p] = max(m[lp], m[rp]); lm[p] = max(max(lm[lp], lm[rp]), val[lp] + m[rp]); if (lm[p] == lm[lp]) lm_node[p] = lm_node[lp]; else if (lm[p] == lm[rp]) lm_node[p] = lm_node[rp]; else lm_node[p] = val_node[lp]; mr[p] = max(max(mr[lp], mr[rp]), m[lp] + val[rp]); if (mr[p] == mr[lp]) mr_node[p] = mr_node[lp]; else if (mr[p] == mr[rp]) mr_node[p] = mr_node[rp]; else mr_node[p] = val_node[rp]; lmr[p] = max(max(lmr[lp], lmr[rp]), max(lm[lp] + val[rp], val[lp] + mr[rp])); if (lmr[p] == lmr[lp]) node1[p] = node1[lp], node2[p] = node2[lp]; else if (lmr[p] == lmr[rp]) node1[p] = node1[rp], node2[p] = node2[rp]; else if (lmr[p] == lm[lp] + val[rp]) node1[p] = lm_node[lp], node2[p] = val_node[rp]; else node1[p] = val_node[lp], node2[p] = mr_node[rp]; } void build(int p, int l, int r) { if (l == r) { int u = dfn[l]; val[p] = dis[u]; lazy[p] = 0; m[p] = -2 * dis[u]; lm[p] = mr[p] = -dis[u]; lmr[p] = 0; val_node[p] = lm_node[p] = mr_node[p] = u; return; } int mid = l + r >> 1; build(lp, l, mid); build(rp, mid + 1, r); pushup(p); } void tag(int p, ll w) { val[p] += w; lazy[p] += w; lm[p] -= w; mr[p] -= w; m[p] -= 2 * w; } void pushdown(int p) { if (lazy[p]) { tag(lp, lazy[p]); tag(rp, lazy[p]); lazy[p] = 0; } } void update(int p, int l, int r, int x, int y, ll w) { if (x <= l && y >= r) { tag(p, w); return; } pushdown(p); int mid = l + r >> 1; if (x <= mid) update(lp, l, mid, x, y, w); if (y > mid) update(rp, mid + 1, r, x, y, w); pushup(p); } ll query(int p, int l, int r, int pos) { if (l == r) return val[p]; pushdown(p); int mid = l + r >> 1; ll ans = 0; if (pos <= mid) ans = query(lp, l, mid, pos); else ans = query(rp, mid + 1, r, pos); pushup(p); return ans; } } seg; void dfs(int u, int pre = 0, int d = 0, ll w = 0) { dep[u] = d; fa[u] = pre; dis[u] = w; dfn[in[u] = ++tol] = u; for (auto p: G[u]) { int v = p.se; ll c = p.fi; if (v == pre) continue; weight[v] = c; dfs(v, u, d + 1, w + c); dfn[++tol] = u; } out[u] = tol; } void init() { lg[1] = 0; for (int i = 2, j = 0; i < N; i++) lg[i] = (i == (1 << (j + 1))) ? ++j : j; for (int i = 1; i <= tol; i++) st[i][0] = i; for (int j = 1; (1 << j) <= tol; j++) for (int i = 1; i + (1 << j) - 1 <= tol; i++) st[i][j] = dep[dfn[st[i][j - 1]]] < dep[dfn[st[i + (1 << (j - 1))][j - 1]]] ? st[i][j - 1] : st[i + (1 << (j - 1))][j - 1]; } ll get(int u, int v) { int x = in[u], y = in[v]; if (x > y) std::swap(x, y); int logg = lg[y - x + 1]; int lca = (dep[dfn[st[x][logg]]] < dep[dfn[st[y - (1 << logg) + 1][logg]]]) ? st[x][logg] : st[y - (1 << logg) + 1][logg]; return seg.query(1, 1, tol, x) + seg.query(1, 1, tol, y) - 2 * seg.query(1, 1, tol, lca); } int main() { //freopen("in.txt", "r", stdin); read(n); for (int i = 1; i < n; i++) { int u, v; ll w; read(u, v, w); G[u].push_back(std::pii(w, v)); G[v].push_back(std::pii(w, u)); edge[i] = {u, v}; } dfs(1); init(); seg.build(1, 1, tol); int q; read(q); char opt[5] = {}; for (; q--; ) { scanf("%s", opt); if (opt[0] == ‘C‘) { int k; ll w; read(k, w); int u = (fa[edge[k].u] == edge[k].v) ? edge[k].u : edge[k].v; seg.update(1, 1, tol, in[u], out[u], w - weight[u]); weight[u] = w; } else { int u; read(u); int x = seg.node1[1], y = seg.node2[1]; printf("%lld ", max(get(x, u), get(y, u))); } } return 0; }
看到有别的写法,维护区间直径,一个点的时候就是直径端点就是自己,两个区间合并的时候,新的直径肯定是一个区间的某个直径端点和另一个区间的某个直直径端点,就取个max合并就好了。
不过是2log的。
以上是关于欧拉序动态维护树直径的主要内容,如果未能解决你的问题,请参考以下文章
HDU 4836 The Query on the Tree lca || 欧拉序列 || 动态树
二叉树有关习题整理543二叉树的直径 606根据二叉树创建字符串 KY11二叉树遍历 - 牛客105从前序遍历与中序遍历构造二叉树
lca 欧拉序+rmq(st) 欧拉序+rmq(线段树) 离线dfs