给一棵n个节点的树,覆盖m条树链,统计每个顶点和每条边被覆盖了多少次。
dfs1:标记深度、子树大小、找重儿子
dfs2:轻重链剖分
chain:覆盖一条树链
calc:统计每个顶点和每条边被覆盖了多少次
const int MAXN = 3e5 + 5;
int n, m;
vector<int> G[MAXN];
int dep[MAXN], siz[MAXN], mch[MAXN], pat[MAXN], top[MAXN];
void dfs1(int u, int p) {
dep[u] = dep[p] + 1, siz[u] = 1, mch[u] = 0, pat[u] = p;
for (int v : G[u]) {
if (v == p)
continue;
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[mch[u]])
mch[u] = v;
}
}
void dfs2(int u, int p, int t) {
top[u] = t;
if (mch[u])
dfs2(mch[u], u, t);
for (int v : G[u]) {
if (v == p || v == mch[u])
continue;
dfs2(v, u, v);
}
}
int lca(int u, int v) {
for (; top[u] != top[v]; u = pat[top[u]]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v);
}
return (dep[u] <= dep[v]) ? u : v;
}
int A[MAXN], B[MAXN];
void chain(int x, int y) {
int z = lca(x, y);
++A[x], ++A[y], --A[z];
if (pat[z])
--A[pat[z]];
++B[x], ++B[y], B[z] -= 2;
}
void calc(int u, int p) {
for (int v : G[u]) {
if (v == p)
continue;
calc(v, u);
A[u] += A[v];
B[u] += B[v];
}
}
void solve() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
G[i].clear();
A[i] = 0, B[i] = 0;
}
for (int i = 1; i <= n - 1; ++i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].emplace_back(y);
G[y].emplace_back(x);
}
dfs1(1, 0);
dfs2(1, 0, 1);
for (int i = 1; i <= m; ++i) {
int x, y;
scanf("%d%d", &x, &y);
chain(x, y);
}
calc(1, 0);
for (int i = 1; i <= n; ++i)
printf("A[%d] = %d
", i, A[i]);
for (int i = 2; i <= n; ++i)
printf("B[%d] = %d
", i, B[i]);
}
对比
树上差分:关键是快速找LCA的算法,可以使用树上倍增、轻重链剖分、Tarjan等不同处理。一般是O(nlogn)解决链修改之后的静态查询问题,但要求修改操作存在逆元。(貌似可以在另一棵树上修改达到子树修改的效果)
轻重链剖分+线段树:O(nlog2n)解决链修改、子树修改的动态查询问题,不需要操作存在逆元。
LCT:O(nlogn)解决链修改的动态查询问题,不需要操作存在逆元。