CodeForces 708C 题解
题解笔记
CF708C Centroids
时间限制 \(4s\) | 内存限制 \(500MB\)
题目描述:
给定一棵树,你有一次将树改造的机会,改造的意思是删去一条边,再加入一条边,保证改造后还是一棵树。
请问有多少点可以通过改造,成为这棵树的重心?(如果以某个点为根,每个子树的大小都不大于 \(\lfloor \dfrac{n}{2} \rfloor\),则称某个点为重心)
输入格式:
第一行一个正整数 \(n\) (\(2 \leq n \leq 400000\))
后面 \(n-1\) 行描述这一棵树的各条边
输出格式:
输出 \(n\) 个数,用空格隔开,第 \(i\) 个数表示第 \(i\) 个点在树经过改造后能否成为重心
输入输出样例:
| 样例1输入 | 样例1输出 | 样例2输入 | 样例2输出 |
|---|---|---|---|
| 3 1 2 2 3 |
1 1 1 | 5 1 2 1 3 1 4 1 5 |
1 0 0 0 0 |
解题思路:
首先分析题目中的“改造”,如何改造?
假设我们想让 \(u\) 成为重心(\(u\) 不是重心)
考虑题目中重心的定义, \(u\) 不是重心肯定是它的某一个子节点的子树超过了\(\lfloor \dfrac{n}{2} \rfloor\)
注意:一定是一棵这样的子树超标,如果两棵子树都超标,那总大小就至少为 \(\lfloor \dfrac{n}{2} \rfloor+1+\lfloor \dfrac{n}{2} \rfloor+1+1\) ,显然大于了 \(n\)
所以要想让 \(u\) 成为重心,就需要从超标子树中抽取一棵符合要求的子树,将它连到 \(u\) 上
这时如果超标子树依然超标,那就恭喜你,这个点改造后也成不了重心
否则就可以为重心
接下来考虑如何快速求出某一点的超标子树,
发现题目里还有一个点没有被利用,就是这是一棵无根树,
于是我们想到对于每一个点来说,它可以有多个子节点,却只有一个父节点,所以考虑让每一个点的父节点以上的子树超标
为了下文方便,我们将 \(u\) 的父节点以上的子树称为 \(u\) 的向上子树
例如下图中 \(2\) 的向上子树如下所示:

所有加粗的节点都是 \(2\) 的向上子树里的
而对于什么样的树每个节点的子节点的子树都不会超标
那就是以重心为根的树
所以就先写一个 dfs 用于找根,就是找重心,这里为了好找,先将根设为 \(1\)
(详细代码请见《参考代码》)
下文将重心称为 rt
接下来,开始计算以 rt 为根的树的各个子树的大小,即 sz 数组
这就是第二次 dfs 的第一个目的
接下来开始分析 dp ,受到上文分析的启发,可以想到设 dp[i] 为 \(i\) 的向上子树中最大的不超标的子树
那对于 \(i\) ,判断它是否可以被改造为重心就可以用 n-sz[i]-dp[i]<=n>>1||i==rt 来计算
为了计算 dp 只好再建立一个辅助数组 mxt[i] 表示子树 \(i\) 中最大的不超过 \(\lfloor \dfrac{n}{2} \rfloor\) 的子树
然后在 dpdfs 的过程中,维护 famx 变量,表示祖先中最大子树的大小
所以 dp[u]=famx ,接下来如果向上子树大小不超标,就可以 famx=max(famx,n-sz[u]) ,
这里的 n-sz[u] 就是向上子树的大小
然后遍历子节点,如何用 mxt 更新 famx 呢?
如下所示:

如图所示,我们从 \(2\) 遍历子节点,遍历到了 \(8\) ,毫无疑问,\(8\)的祖先中最大的非超标子树应该是 \(3\) 往下的子树,可以发现,这里的子树不是向上子树,所以在更新 famx 时也需要 mxt ,这里的子树正是 mxt[1] ,
所以 dpdfs(u,v,max(famx,sz[mxt[u]]))
就完成了吗?
如果觉得完成了就过于相信题解了
显然没有,如果 mxt[u]==v 怎么办呢?
所以需要维护 mxt[u] 的最大值以及次大值,将 mxt 数组扩一维:mxt[i][0/1]
然后再 dpdfs 就行了
那 mxt 数组再什么时候计算呢?
运行 cntsz 函数时一起计算了,就可以了
参考代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=4e5+7;
int n,dp[MAXN],mxt[MAXN][2],sz[MAXN],w=0x3f3f3f3f,rt;
//dp[i]表示除了子树i外最大的子树大小(由于以rt为根,所以大小不会超过n/2)
//mxt用于求主dp的辅助dp,mxt[i]表示子树i中最大以及次大的不超过n/2的子树
struct Edge
{
int v,nxt;
} e[MAXN<<1];
int head[MAXN],tot;
void add(int u,int v)
{
e[++tot].v=v;
e[tot].nxt=head[u];
head[u]=tot;
}
int find_rt(int fa,int u)//第一次dfs,寻找重心,假定1为根,返回当前子树的大小
{
int sum=1,uw=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(v!=fa)
{
int s=find_rt(u,v);
if(s==-1) return -1;
sum+=s;
uw=max(uw,s);
}
}
uw=max(uw,n-sum);
if(uw<=n>>1) return rt=u,-1;
return sum;
}
void cntsz(int fa,int u)//计算size,顺便维护mxt数组
{
sz[u]=1;//将u节点自己包含进去
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(v==fa) continue;
cntsz(u,v);
sz[u]+=sz[v];
if(sz[v]<=n>>1)
{
if(sz[v]>sz[mxt[u][0]]) mxt[u][1]=mxt[u][0],mxt[u][0]=v;
else if(sz[v]>sz[mxt[u][1]]) mxt[u][1]=v;
}
}
}
void dpdfs(int fa,int u,int famx)//通过dfs进行dp;famx表示祖先中最大子树的大小
{
dp[u]=famx;
if(n-sz[u]<=n>>1) famx=max(famx,n-sz[u]);
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(v==fa) continue;
if(v==mxt[u][0]) dpdfs(u,v,max(famx,sz[mxt[u][1]]));
else dpdfs(u,v,max(famx,sz[mxt[u][0]]));
}
}
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);
}
find_rt(0,1);
//printf("rt:%d\n",rt);
cntsz(0,rt);
dpdfs(0,rt,0);
//for(int i=1;i<=n;i++) printf("%d:sz:%d,dp:%d\n",i,sz[i],dp[i]);
for(int i=1;i<=n;i++) printf("%d ",n-sz[i]-dp[i]<=n>>1||i==rt);
return 0;
}

浙公网安备 33010602011771号