CF708C Centroids(树形dp)

Centroids - 洛谷https://www.luogu.com.cn/problem/CF708C这是一道cf2300的树形dp题,个人感觉十分妙,所以特此写题解记录一下好题.

思路:针对于这个重心的定义,我们进行分情况讨论(设dfs我们当前遍历的点为x,发亲节点为fa):

首先可以确定的一件事是,在这整棵树里面,我们选定一个点时,肯定只会有一颗子树的大小>=n/2.所以我们只需要考虑砍哪一颗即可.分情况讨论:

1.当x的所有子树的大小都<=n/2并且n减去包含x这棵子树之后剩下的大小(这个大小其实是如果我们认定x为根节点的另一棵子树的大小)也是<=n/2的那么这个点肯定可以作为重心.

2.当x的某棵子树大小>n/2,那么我们就要考虑减去这棵子树中最大的<=n/2的子树之后这棵子树的大小<=n/2那么肯定也是成立的,反之即使减去了最大的<=n/2的子树还是不符合条件就肯定不成立(ps:这棵被减去的小子树被插到了x的下方成为了一颗新的x的子树.).为了实现这个判断,我们考虑维护一个f[x],f[x]=max(f[y],siz[x]).y是x的子节点,满足之前的都<=n/2的情况.

3.第三种情况就有点难处理了.我们知道如果以x为重心也就是考虑它为根节点,那么在现实情况的子树意外,它的现实情况的父亲节点的上方是x为重心时候的子树.所以我们考虑x的上方踩在>n/2的子树的情况,和第二种情况类似.但是有一个问题.我们无法位数出上方的f[fa].但是我们可以维护出每个点的f的次大和最大值f[x][0/1].这样的话假设x所在的fa的子树时如果f值大于区域子树,我们就可以取次大值f[x][1]作为"最大值"进行类似于情况2的判断.当x所在的子树大小不是最大的时,我们只需要考虑f[x][0]最大值即可.

针对这上述情况判断即可

下面上代码,有注释:

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10,mod=998244353;
int h[N],n,ans[N],f[N][3],siz[N],maxson[N],maxpos[N],selectmax[N],tot=0;
/*
f[x][0/1]维护的是x的子树中次大和最大的大小<=n/2的子树的大小

maxpos维护的是x的最大值是哪一个儿子

maxson维护的是针对于这个点我们切除的最大的<=n/2的树的大小

selectmax是方便判断,当x上方的父亲节点所在的树索要切割的
"最大"的最大子树的是哪个.如果最大是x所在的树我们就算则次大值判断
反之选最大值判断
*/
struct node
{
    int to,ne;
}edge[2*N];
void add(int x,int y)
{
    edge[++tot].to=y;
    edge[tot].ne=h[x];
    h[x]=tot;
    return ;
}
void dfs1(int x,int fa)
{
    maxpos[0]=0;
    f[x][0]=f[x][1]=1;
    siz[x]=1;//初始化
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int y=edge[i].to;
        if(y==fa)
            continue;
        dfs1(y,x);
        siz[x]+=siz[y];
        int v;
        if(siz[y]>siz[maxpos[x]])
            maxpos[x]=y;//记录最大的儿子
        if(siz[y]<=n/2)
            v=siz[y];
        else if(f[y][0]<=n/2)
            v=f[y][0];
        if(v>=f[x][0])
        {
            selectmax[x]=y;//记录我们所选的f的最大是谁
            f[x][1]=f[x][0];
            f[x][0]=v;
        }
        else if(v>=f[x][1])
            f[x][1]=v;
        //更新最大次大值
    }
    return ;
}
void dfs2(int x,int fa)
{
    ans[x]=1;
    if(siz[x]>n/2&&siz[maxpos[x]]-f[maxpos[x]][0]>n/2)
        ans[x]=0;
    //此处判断的是siz[x]>n/2所以我们从x的子树中切除某棵子树的情况
    //我们减去最大儿子的某棵子树,只要减去之后剩下的权重>n/2就不成立
    else if(siz[x]<=n/2&&n-siz[x]-maxson[x]>n/2)
    //此处判断的是siz[x]<=n/2所以我们从x的上方fa切除某棵子树(不包括含x的子树)
    //当减去了含x子树和上方的需要减去的子树剩下的树权重>n/2就不成立
        ans[x]=0;
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int y=edge[i].to,num;
        if(y==fa)
            continue;

        if(n-siz[x]>n/2) 
            num=maxson[x];
		else 
            num=n-siz[x];
        maxson[y]=max(maxson[y],num);
        //更新最大剪去数值的大小
        if(selectmax[x]==y)
            maxson[y]=max(maxson[y],f[x][1]);
        //当fa的最大的f的值是当前这个子节点,那么要去次大值,因为最大值不能包含这个点
        else
            maxson[y]=max(maxson[y],f[x][0]);
        //反之,这个点可以选,直接去最大值即可
        dfs2(y,x);
    }
    return ;
}
void solve()
{
    int x,y;
    memset(h,-1,sizeof h);
    cin>>n;
    for(int i=1;i<n;i++)
    {   
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    dfs1(1,0);
    dfs2(1,0);
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
    cout<<endl;
    return ;
}
signed main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    solve();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值