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;
}
posted @ 2022-10-15 15:42  Kobe303  阅读(30)  评论(0编辑  收藏  举报