CF 708C. Centroids 树形dp

题目


题意:

给出一棵树(n<=4e5),可以有一次机会:删除一条边,然后添加一条边,使之仍然是树。
现在对于每个点,最多有一次机会,是否可以保证与它相连的子树大小不超过n/2。(删掉这个点后,所有连通分量的结点数<=n/2)


解法:

1.删去任一结点x,最多只会有一棵树的size>=n/2
2.对于这棵树的补救措施就是将其中一部分移到别处。
3.只能是一部分,因为全部移到别处更加不能满足要求。
4.删去的一部分也是一棵树。
5.需要将这棵树的根结点作为x的子结点,这样才是最优的。

两次dfs,依次处理出dp[x](子树x的size),up[x] (=n-dp[x])

cutd[x] (对于子树x,能移除的树的最大size,注意x不能移除,否则不满足还是不满足),注意这个大小要控制在[0,n/2]内,否则移动到哪都不行。这棵树必须是x的后代结点)

cutu[x] (x的祖辈中,就是x往上走,能移除的树最大size,注意不包括x本身)


代码:

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;

#define all(x) (x).begin(), (x).end()
#define for0(a, n) for (int (a) = 0; (a) < (n); (a)++)
#define for1(a, n) for (int (a) = 1; (a) <= (n); (a)++)
#define mes(a,x,s)  memset(a,x,(s)*sizeof a[0])
#define mem(a,x)  memset(a,x,sizeof a)
#define ysk(x)  (1<<(x))
typedef long long ll;
typedef pair<int, int> pii;
const int INF =0x3f3f3f3f;
const int maxn=400000    ;
int n;
vector<int >G[maxn+5];
int dp[maxn+5],up[maxn+5];
int ok[maxn+5],cutd[maxn+5],cutu[maxn+5];
void dfs(int x,int fa)
{
   int siz=G[x].size(),ret=0;
   dp[x]=1;cutd[x]=0;
   ok[x]=1;
   for0(i,siz)
   {
       int y=G[x][i];if(y==fa)  continue;
       dfs(y,x);
       dp[x]+=dp[y];
       if(dp[y]>n/2)
       {
           if( dp[y]-cutd[y]>n/2  )  ok[x]=-1;
       }
       cutd[x]=max(cutd[x],cutd[y]);
       if(dp[y]<=n/2)  cutd[x]=max(cutd[x],dp[y]);
   }
   up[x]=n-dp[x];
   if(up[x]>n/2) ok[x]=0;


}

int pre[maxn+5],suf[maxn+5],pos[maxn+5],np;
void dfs2(int x,int fa)
{
    int siz=G[x].size(),np=0;

    for0(i,siz)
    {
        int y=G[x][i];if(y==fa) continue;
        pos[++np]=y;
    }
    pre[x]=0;
    for1(i,np)
    {
        int y=pos[i];
        int ret=cutd[y ];
        if(dp[y]<=n/2)  ret=max(ret,dp[y]);
        pre[i]=max(pre[i-1],ret);
    }

    suf[np+1]=0;
    for(int i=np;i>=1;i--)
    {
        int y=pos[i];
        int ret=cutd[y ];
        if(dp[y]<=n/2)  ret=max(ret,dp[y]);
        suf[i]=max(suf[i+1],ret);
    }


    for1(i,np)
    {
        int y=pos[i];
        int tmp=max(pre[i-1],suf[i+1]);
        tmp=max(tmp,cutu[x]);
        if(up[y]<=n/2) tmp=max(tmp,up[y]);
        cutu[y]=tmp;
        if(!ok[y] &&up[y]-cutu[y] <=n/2)  ok[y]=1;
    }

    for0(i,siz)
    {
        int y=G[x][i];if(y==fa) continue;
        dfs2(y,x);
    }
}
int main()
{
   std::ios::sync_with_stdio(false);
   int x,y;
   while(cin>>n)
   {
       for1(i,n) G[i].clear();
       for0(i,n-1)
       {
           cin>>x>>y;
           G[x].push_back(y);G[y].push_back(x);
       }
       dfs(1,-1);
       cutu[1]=0;dfs2(1,-1);
       for1(i,n)
       {
           putchar( ok[i]==1?'1':'0');
           putchar( i==n?'\n':' ');
       }

   }
   return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值