zoukankan      html  css  js  c++  java
  • CSP 2019 树的重心

    CSP 2019 树的重心

    题解

    方法一

    一棵根为 (u) 的树的重心,只有可能有两种情况:

    • (u) 的重儿子内
    • (u) 就是重心

    所以可以考虑每回跳重儿子以寻找重心。枚举每条边,现在用一个节点 (u) 代表 (u) 到他父亲的边。

    这条边去掉后两边的树为 (u) 的子树和剩下的,包含 (u) 的父亲的那棵树。

    首先考虑如何求出 (u) 子树内的重心。考虑倍增 (f_{i,j}) 表示从 (i) 开始跳重儿子,跳 (2^j) 次到哪里。

    其次时如何求出 (u) 的父亲的那棵树的重心。还是使用倍增。考虑边换根边修改倍增数组,变化只有 (f_{fa_u,i}) 会产生变化。只是倍增的起点应该是 (u) 的父亲而非根节点,这样每次转换 (u) 时倍增数组和每个节点的重儿子在 (u ightarrow v) 时的变化可以处理。

    不过每回 dfs 到一个节点 (v) 回溯得时候要将倍增数组调回 dfs 到 (u) 时的样子。

    方法二

    考虑统计一个点能作为删掉哪些边后的重心。

    对于一条边 ((u,v)) ,删掉是否可以让 (x) 作为重心呢?

    如果 (x) 不是重心,那么 ((u,v)) 必然在删掉 (x) 后的最大连通块内。考虑删掉 ((u,v)) 的意义就是去掉最大子树的一部分。设去掉的大小为 (S) ,一个节点的子树大小为 (s_x) ,最大子树为 (g_x) 。那么去掉 ((u,v)) 后可以让 (x) 成为重心必然满足:

    • (displaystyle lfloor frac{n-S}{2} floorge g_x)
    • (displaystyle lfloor frac{n-S}{2} floorge n-s_x-S)

    那么可以解出来 (n-2s_xle Sle n-2g_x) 。而且不能去掉 (x) 的子树内的结点。

    所以其实就是有一个 dfs 序的限制,一个子树大小的限制。直接离线+树状数组即可。

    细节

    细节主要在于换根。将结点 (u ightarrow v) 时会改变的只有 (u) 这一个点的重儿子(假设现在 (u)(v) 的父亲,要向下换根)。具体是将 (u) 的儿子换为除了 (v) 以外的与 (u) 有边的节点中子树大小最大的。

    代码

    #include <cstdio>
    using namespace std;
    const int NN = 3e5 + 5;
    int T, N, head[NN], totE, go[NN][21], up[NN];
    int siz[NN], son[NN], secson[NN], fa[NN];
    struct E {
    	int v, nxt;
    	E(int _v, int _nxt) : v(_v), nxt(_nxt) {}
    	E() : v(0), nxt(0) {}
    } edge[NN * 2];
    void AddE(int u, int v) {
    	edge[++totE] = E(v, head[u]);
    	head[u] = totE;
    }
    void calc(int u) {
    	for (int i = 1; i <= 18; ++i)
    		go[u][i] = go[go[u][i - 1]][i - 1];
    }
    void dfs1(int u, int f) {
    	siz[u] = 1;
    	fa[u] = f;
    	up[u] = f;
    	for (int p = head[u]; p; p = edge[p].nxt) {
    		int v = edge[p].v;
    		if (v != f) {
    			dfs1(v, u);
    			siz[u] += siz[v];
    			if (siz[son[u]] < siz[v]) {
    				secson[u] = son[u];
    				son[u] = v;
    			} else if (siz[v] > siz[secson[u]]) //这里是 else if
    				secson[u] = v;
    		}
    	}
    	go[u][0] = son[u];
    	calc(u);
    }
    long long ans;
    void Find(int u) {
    	int now = u;
    	for (int i = 18; i >= 0; --i)
    		if (go[now][i] && (siz[u] - siz[go[now][i]]) * 2 <= siz[u])
    			now = go[now][i];
    	ans += now;
    //	printf("u %d
    ", u);
    //	printf("now %d ", now);
    	if (siz[now] * 2 == siz[u]) {
    		ans += fa[now];
    //		printf("fa %d", fa[now]);
    	}
    //	printf("
    ");
    }
    void dfs2(int u, int f) {
    	if (u != 1) {
    		siz[f] = siz[f] - siz[u] + siz[up[f]];
    		//注意 up 和 fa 的使用
    		int ffsz = siz[up[f]], hvson = up[f], oth = (son[f] == u) ? secson[f] : son[f];
    		if (ffsz < siz[oth]) hvson = oth;
    		go[f][0] = hvson;
    		calc(f);
    		fa[f] = u;
    		fa[u] = 0;
    		Find(u);
    		Find(f);
    //		printf("
    ");
    	}
    	for (int p = head[u]; p; p = edge[p].nxt) {
    		int v = edge[p].v;
    		if (v != f) {
    			dfs2(v, u);
    		}
    	}
    	siz[f] = siz[f] + siz[u] - siz[up[f]];
    	fa[u] = f;
    	fa[f] = 0;
    	go[f][0] = son[f];
    	calc(f);
    }
    int main() {
    	freopen("centroid.in", "r", stdin);
    	freopen("centroid.out", "w", stdout);
    	scanf("%d", &T);
    	while (T--) {
    		scanf("%d", &N);
    		for (int i = 1; i < N; ++i) {
    			int u, v;
    			scanf("%d%d", &u, &v);
    			AddE(u, v);
    			AddE(v, u);
    		}
    		dfs1(1, 0);
    		dfs2(1, 0);
    		printf("%lld
    ", ans);
    		ans = 0;
    		totE = 0;
    		for (int i = 0; i <= N; ++i) {
    			head[i] = son[i] = siz[i] = secson[i] = fa[i] = up[i] = 0;
    			for (int j = 0; j <= 18; ++j)
    				go[i][j] = 0;
    		}
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

    总结

    以棵以 (u) 为根节点的树,重心要么是 (u) ,要么在 (u) 的重儿子里。

  • 相关阅读:
    做才是得到
    常用工具汇总
    迎接2017
    新年礼物
    2017
    asp.net core 日志
    板子|无向图的割点
    11/06信竞快乐模拟赛
    动态规划复习
    894D
  • 原文地址:https://www.cnblogs.com/YouthRhythms/p/13763745.html
Copyright © 2011-2022 走看看