CodeChef COOK130A Clamp Paths on Tree
考虑建出原树的点 Kruskal 重构树,也就是每次取出连通块的最值作为根,然后删去根裂成若干个连通块,递归构造这些连通块,最后在新图中将这些连通块的根与整个连通块的根连边。
容易发现,这样建出的新树满足 \(a,b\) 在原树上的链上最值等于新树上的 \(\text{lca}(a,b)\)。
于是建出最小值的树和最大值的树,条件变为:\(a,b\) 在两棵树上都要是祖先关系。
求出一棵树的 \(dfn\),在另一棵树上 DFS,用树状数组维护当前点到根路径上所有点的 \(dfn\),合法的祖先可以直接区间查询。
时间复杂度 \(\mathcal O(n\log n)\)。
具体细节看代码。
Code:
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef vector <int> vi;
typedef long long ll;
const int N = 1000005;
int n; ll ans;
int st[N], ed[N], tim;
vi e[N], G1[N], G2[N];
int fa[N];
void init() { for (int i = 1; i <= n; ++i) fa[i] = i; }
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) { x = find(x), y = find(y); if (x == y) return; fa[x] = y; }
int c[N];
void add(int x, int y) { for (; x <= n; x += x & -x) c[x] += y; }
int query(int x) { int res = 0; for (; x; x -= x & -x) res += c[x]; return res; }
void dfs1(int u) {
st[u] = ++tim;
for (int v : G1[u]) dfs1(v);
ed[u] = tim;
}
void dfs2(int u) {
ans += query(ed[u]) - query(st[u] - 1);
add(st[u], 1);
for (int v : G2[u]) dfs2(v);
add(st[u], -1);
}
int main() {
scanf("%d", &n);
for (int i = 1, u, v; i < n; ++i) scanf("%d%d", &u, &v), e[u].pb(v), e[v].pb(u);
init();
for (int u = 1; u <= n; ++u)
for (int v : e[u]) if (v < u)
G1[u].pb(find(v)), merge(v, u);
init();
for (int u = n; u; --u)
for (int v : e[u]) if (v > u)
G2[u].pb(find(v)), merge(v, u);
dfs1(n), dfs2(1);
printf("%lld", ans);
return 0;
}