Codeforces321 C. Ciel the Commander(点分治)

题意:

给定n个点的树,要求给每个节点一个’A’-'Z’的字符
使得树满足:
对于任意两点a和b,如果点a和点b上面的字符一样,
那么a到b的路径上必须存在一个点c,
点c需要满足字符大于点a和b上的字符.
(本题中’A’最大,'Z’最小)

输出方案,如果无解输出impossible

数据范围:n<=1e5

解法:
显然最多只有一个点标记'Z',因为没有比'Z'更大的字符了
如果存在两个'Z',没办法在他们中间放更大的

考虑在哪个位置放'Z'最优,
如果我们在点x位置放置了'Z',那么对于点x的所有子树,都不能放'Z',
问题变成用'A''Y'处理点x的每棵子树,这是一个子问题.

为了有解,子问题的规模不宜太大,容易想到树的重心,
用树的重心拆分树,最大子树的规模最小,
如果不用重心拆的话,层数过多,容易无解.

因此每次找树的重心,拆分整棵树,然后对每棵子树递归求重心即可,
递归找重心是经典的点分治问题.

每次按照重心拆分,子树规模<=n/2,因此递归总层数为log(n),
第一层用'Z',第二层用'Y',那么超过26层无解,
因为log(1e5)=11<26,所以一定有解

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],cnt;
int sz[maxm],son[maxm];
int mark[maxm];
char ans[maxm];
int size;
int root;
int n;
void add(int x,int y){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void getroot(int x,int fa){
    sz[x]=1;
    son[x]=0;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(v==fa||mark[v])continue;
        getroot(v,x);
        sz[x]+=sz[v];
        son[x]=max(son[x],sz[v]);
    }
    son[x]=max(son[x],size-sz[x]);
    if(son[root]>son[x]){
        root=x;
    }
}
void divide(int x,char p){
    ans[x]=p;
    mark[x]=1;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(mark[v])continue;
        son[root=0]=size=sz[v];
        getroot(v,-1);
        divide(root,p+1);
    }
}
signed main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b;scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    son[root=0]=size=n;
    getroot(1,-1);
    divide(root,'A');
    for(int i=1;i<=n;i++){
        printf("%c ",ans[i]);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值