CF708C Centroids(换根dp)

发布时间 2023-05-04 15:35:38作者: 腾云今天首飞了吗

题意:

给定一颗树,你有一次将树改造的机会,改造的意思是删去一条边,再加入一条边,保证改造后还是一棵树。
请问有多少点可以通过改造,成为这颗树的重心?(如果以某个点为根,每个子树的大小都不大于\(\dfrac{n}{2}\),则称某个点为重心)

思路:

是今天遇到的一道有意思的换根dp呃呃。

从题意来看,最重要的就是如何快速求出某个结点最大的子树,以及这棵子树最多能砍多大一个子子树下来并作为该结点的一个新子树,使得该结点的所有子树的大小不超过 \(\dfrac{n}{2}\)

先以 \(1\) 为根节点。设 \(siz[u]\) 为以 \(u\) 为根节点的子树的大小。设 \(mxn[u]\) 为该结点最大的子树的根节点的编号。设 \(f[u]\) 为结点 \(u\) 所有的最大的、且大小不超过 \(\dfrac{n}{2}\) 的真子树的大小。

image

然而,仅靠 \(f\) 树组并不能覆盖所有情况。以结点 \(2\) 为例,除了由结点 \(4\) \(5\) \(6\) 组成的子树,结点 \(1\) \(3\) \(7\) \(8\) 也可以作为结点 \(2\) 的子树,但因为 \(f\) 的定义,这一部分子树并没有被覆盖到,因此还需要定义一个 \(g\) 树组,记录这类子树最大的、且大小不超过 \(\dfrac{n}{2}\) 的真子树的大小。通过两次 \(dfs\),作下换根dp即可维护这两个树组的值。

接下来看怎么转移。

\(f[u]\) 是比较好维护的,第一遍常规的树形dp就可以做到,其转移如下:

\[ f[u]=\left\{ \begin{matrix} max(f[u],siz[v]) (siz[v] <= \frac{n}{2})\\ max(f[u],f[v]) \end{matrix} \right. \]

对于 \(g[u]\),似乎得分两种情况。
首先,对于所有子结点来说,如果 \(n - siz[v] <= \frac{n}{2}\),那么都有:

\[g[v] = max(g[v],n - siz[v]) \]

如果结点 \(v\) 不是 \(u\) 的所有子结点中siz最大的一个结点,那么还有:

\[g[v] = max(g[v],f[u]) \]

如果结点 \(v\)\(u\) siz最大的一个子结点,就不能用上面的转移方程,由 \(g\)\(f\) 的定义可知,读者不难自证。它只能由 \(u\) 所有的第二大的、且大小不超过 \(\dfrac{n}{2}\) 的真子树的大小转移过来,因此在处理 \(f[u]\) 时,还要记录一个 \(sec[u]\) 记录这个信息。然后有:

\[g[v] = max(g[v],sec[u]) \]

这两个数组处理出来之后,直接判断就能得出答案了。
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5;
int n,tot,head[MAXN + 5],siz[MAXN + 5],f[MAXN + 5],g[MAXN + 5],mxs[MAXN + 5],sec[MAXN + 5],ans[MAXN + 5],mxn[MAXN + 5];
struct EDGE{
    int u,v,next;
}edge[2 * MAXN + 5];
void add(int u,int v){
    ++tot;
    edge[tot] = (EDGE){u,v,head[u]};
    head[u] = tot;
}
void dfs(int u,int fa){
	siz[u] = 1;
	for(int i = head[u]; i; i = edge[i].next)
	{
		int v = edge[i].v;
		if(v == fa)continue;
		dfs(v,u);
		siz[u] += siz[v];
		if(siz[v] > siz[mxn[u]]) mxn[u] = v;
		if(siz[v] > n / 2){
			if(f[v] > f[u]) sec[u] = f[u],f[u] = f[v],mxs[u] = v;
			else if(f[v] > sec[u]) sec[u] = f[v];
		}
		else{
			if(siz[v] > f[u])sec[u] = f[u],f[u] = siz[v],mxs[u] = v;
			else if(siz[v] > sec[u])sec[u] = siz[v];
		}
	}
}
void dfs1(int u,int fa){
    ans[u] = 1;
    if(siz[mxs[u]] > n / 2)ans[u] = ((siz[mxs[u]] - f[mxs[u]]) <= n / 2);
    if(n - siz[u] > n / 2)ans[u] = ((n - siz[u] - g[u]) <= n / 2);
    for(int i = head[u]; i; i = edge[i].next){
        int v = edge[i].v;
        if(v == fa)continue;
        g[v] = max(g[u],g[v]);
        if(n - siz[v] <= n / 2)g[v] = max(g[v],n - siz[v]);
        if(v == mxs[u])g[v] = max(g[v],sec[u]);
        else g[v] = max(g[v],f[u]);
        dfs1(v,u);
    }
}
int main(){
    scanf("%d",&n);
    for(int i = 1; i < n; i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs(1,1);
    dfs1(1,1);
    for(int i = 1; i <= n; i++){
        cout << ans[i] << " ";
    }
}