CF708C Centroids(换根dp)

题意:

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

思路:

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

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

先以 1 为根节点。设 siz[u] 为以 u 为根节点的子树的大小。设 mxn[u] 为该结点最大的子树的根节点的编号。设 f[u] 为结点 u 所有的最大的、且大小不超过 n2 的真子树的大小。

image

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

接下来看怎么转移。

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

f[u]={max(f[u],siz[v])(siz[v]<=n2)max(f[u],f[v])

对于 g[u],似乎得分两种情况。
首先,对于所有子结点来说,如果 nsiz[v]<=n2,那么都有:

g[v]=max(g[v],nsiz[v])

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

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

如果结点 vu siz最大的一个子结点,就不能用上面的转移方程,由 gf 的定义可知,读者不难自证。它只能由 u 所有的第二大的、且大小不超过 n2 的真子树的大小转移过来,因此在处理 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] << " "; } }

__EOF__

本文作者Never Gonna Give You Up!
本文链接https://www.cnblogs.com/CZ-9/p/17368432.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   腾云今天首飞了吗  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示