• CF708C Centroids(换根dp,树的重心)


    CF708C Centroids

    其实这道题换根的时候不需要再维护一个次大值,提供一种维护重儿子的做法。

    我们来考虑暴力怎么做?

    枚举每个点,如果它的最大的子树都不大于(frac{n}{2})的话那它肯定可以(已经可以那断一条边再连上即可)。注意这里是不大于所以直接下取整对答案没有影响。

    先统计一遍大于(frac{n}{2})的子树的个数,首先这个个数肯定不会大于(1)(这不废话吗?)。然后对于那个大于(frac{n}{2})的儿子,它肯定是子树大小最大的儿子,这不就是重链剖分中的重儿子吗?所以我们每个点还可以记录一个重儿子。

    下文删掉的意思是将其连向父亲的边删掉然后连向根节点。

    如果一个点的重儿子将其重儿子的子树删掉后还没有小于等于(frac{n}{2})那么显然这个点就不行。

    那如果满足情况呢?我们发现如果其本身大小也大于(frac{n}{2})的话也不行。那如果删其他儿子的话一定也会大于(frac{n}{2})了(因为重儿子已经大于(frac{n}{2})),所以一定还得删重儿子的儿子,同理最优还是删重儿子的重儿子(有点绕...),而如果不行则删其他儿子也不行。

    故我们需要在重链上找一个子树大小小于等于(frac{n}{2})的点,而这个点必然越浅越好(子树大小越大越好)。这样我们将重链放在一个set中维护然后找到第一个小于等于(frac{n}{2})的点,判断减去它能否小于(frac{n}{2})即可。

    然后各种DS套上去就行了,但是我们不想写DS怎么办(其实是我不会),其实就是求一个子树内子树大小小于等于(frac{n}{2})且最大的点,这不是dp就好了吗?(这也是为什么这题难度是蓝的原因)

    (dp_x)(x)子树内子树大小小于等于(frac{n}{2})且最大的点的子树大小(不包括(x)),则根据上面的推导有(dp_x=max(dp_{son_x}, size_{son_x}(size_{son_x} le frac{n}{2})))。其中(son_x)代表(x)的重儿子。

    然后换根求dp值,按我上面的做法判断即可。

    具体怎么换根呢?

    如果我们现在想要把根从(x)换成(y),可以分成两类讨论。

    首先不管哪一类都要将(x)(y)(size)改变,具体实现就是换根dp的基础了吧。

    第一种情况:(y)不是(x)的重儿子。那这种情况(x)的重儿子不会改变,只要判断(y)的重儿子有没有改成(x),然后更新(y)的重儿子与dp值。

    第二种情况:(y)(x)的重儿子。此时(x)的重儿子会改变(变成除了(y)以外与(x)相连的一个点),那么只要遍历一遍与(x)相邻的点重新求一下(x)的重心并更新dp值。重复情况一(y)的更新。

    时间复杂度:(O(n+m)),因为只有一个重儿子所以遍历也只会遍历一遍,又每个边也只会被遍历两遍所以总的时间复杂度是(O(m))级别的。

    Code:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 400010;
    namespace IO{
    	template <typename T> void read(T &x) {
    		T f = 1;
    		char ch = getchar();
    		for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
    		for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    		x *= f;
    	}
    } using namespace IO;
    namespace ZCR{
    	struct node{
    		int pre, to;
    	}edge[N << 1];
    	int head[N], tot;
    	int n, goal;
    	int dp[N], sz[N], son[N];
    	int ans[N];
    	void add(int u, int v) {
    		edge[++tot] = node{head[u], v};
    		head[u] = tot;
    	}
    	void change(int x) {
    		if (son[x]) {
    			dp[x] = dp[son[x]];
    			if (sz[son[x]] <= goal) dp[x] = sz[son[x]];
    		}
    	}
    	void dfs1(int x, int fa) {
    		sz[x] = 1;
    		for (int i = head[x]; i; i = edge[i].pre) {
    			int y = edge[i].to;
    			if (y == fa) continue;
    			dfs1(y, x);
    			sz[x] += sz[y];
    			if (sz[y] > sz[son[x]]) son[x] = y;
    		}
    		change(x);
    	}
    	void change_root(int x, int y) {
    		sz[x] -= sz[y];
    		sz[y] += sz[x];
    		if (y == son[x]) {
    			son[x] = 0;
    			for (int i = head[x]; i; i = edge[i].pre) {
    				int z = edge[i].to;
    				if (z == y) continue;
    				if (sz[z] > sz[son[x]]) son[x] = z;
    			}
    			change(x);
    		}
    		if (sz[son[y]] < sz[x]) {
    			son[y] = x;
    			change(y);
    		}
    	}
    	void dfs2(int x, int fa) {
    		if (sz[son[x]] <= goal || sz[son[x]] - dp[son[x]] <= goal) ans[x] = true;
    		for (int i = head[x]; i; i = edge[i].pre) {
    			int y = edge[i].to;
    			if (y == fa) continue;
    			change_root(x, y);
    			dfs2(y, x);
    			change_root(y, x); 
    		}
    	}
    	void MAIN() {
    		memset(dp, 0xcf, sizeof(dp));
    		read(n);
    		goal = n / 2;
    		for (int i = 1, u, v; i < n; i++) {
    			read(u); read(v);
    			add(u, v);
    			add(v, u);
    		}
    		dfs1(1, 0);
    		dfs2(1, 0);
    		for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
    	}
    } using namespace ZCR;
    int main() {
    	MAIN();
    	return 0;
    }
    

    一遍过了好开心(@^▽^@)

    关于重心本题的另一种解法

  • 相关阅读:
    【Python】小练习
    【C语言】利用二维数组输出成绩
    【C语言】多维数组
    【C语言】输入一个字符串,并对字符串中的偶数位置的字符按从小到大的顺序排序,奇数位置的字符不动,输出排序后的结果
    【C语言】一堆数组中存放了10个小于100的整数,请编程对所有数据按照从小到大的顺序进行排序,若个位数相等,则按照十位从小到大的顺序排序,输出排序后的结果
    【C语言】移动指针
    Python中68个内置函数的总结
    【Python】变量命名习惯
    【Python】 基础语法
    【Python】 注释
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/13221420.html
Copyright © 2020-2023  润新知