跳蚤王国

题目描述

跳蚤王国爆发了一场动乱,国王在镇压动乱的同时,需要在跳蚤国地方钦定一个人来做宰相。
然而当时国王的钦定方式很奇怪,跳蚤王国可以看成一棵树,国王认为宰相必须更好地位跳蚤服务,所以他会选择一个到所有节点距离和最小的节点,并在这个节点中钦定,如果有多个节点满足距离和最小则任选一个。
然而跳蚤国的动乱实在是太厉害了,以至于树的心态可能也会发生改变也就是说,树上可能会有若干条边消失,如果这个情况出现的话一定会有同样数目的边出现,以保证整个结构仍然是一棵树。
现在这个跳蚤想知道每个节点中的跳蚤如果要被钦定,至少需要多少条边消失(当然也有同样数目的边出现)。作为这只跳蚤的一名真正的粉丝,你能帮他解决这个问题吗?

贪心

显然是问删了再加至少多少边使一个点成为重心。
重心的定义是所有儿子子树大小不超过n/2。
切了若干刀后,显然
一个显然的暴力是枚举一个点算它的答案,那么就以它为根。
然后对于一个子树计算f[i][0~1],分别表示至少切多少下,以及切了这么多下后i所在联通块的大小。
那么如何转移了,先需要儿子分别被切到<=n/2的次数,然后按照剩余大小排序,从大往小切,直至它的剩余大小也<=n/2。
这是很显然的贪心吧!但是会T。
我们考虑钦点根后,还是能求出f,而对于一个点x,还需要求一个g,表示除它的子树外的部分要切多少下,可以由父亲转移而来,转移很简单,使用二分找到至少要切多少个子树即可。详见代码。
真是好水的题呢!

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1000000+10;
int h[maxn],go[maxn*2],next[maxn*2],size[maxn],father[maxn],a[maxn];
int f[maxn][2],g[maxn][2],sum[maxn],wz[maxn],sta[80];
int i,j,k,l,r,mid,t,n,m,tot,top,ans;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
bool cmp(int x,int y){
    return f[x][1]>f[y][1];
}
void dfs(int x,int y){
    father[x]=y;
    int t=h[x];
    size[x]=1;
    while (t){
        if (go[t]!=y){
            dfs(go[t],x);
            size[x]+=size[go[t]];
        }
        t=next[t];
    }
    top=0;
    int i;
    t=h[x];
    while (t){
        if (go[t]!=y) a[++top]=go[t];
        t=next[t];
    }
    fo(i,1,top) f[x][0]+=f[a[i]][0],f[x][1]+=f[a[i]][1];
    f[x][1]++;
    sort(a+1,a+top+1,cmp);
    fo(i,1,top){
        if (f[x][1]<=n/2) break;
        f[x][0]++;
        f[x][1]-=f[a[i]][1];
    }
}
void dg(int x,int y){
    int i,j,k=0,l,r,mid,t;
    top=0;
    t=h[x];
    while (t){
        if (go[t]!=y) a[++top]=go[t];
        t=next[t];
    }
    a[++top]=n+1;
    f[n+1][0]=g[x][0];
    f[n+1][1]=g[x][1];
    sort(a+1,a+top+1,cmp);
    fo(i,1,top) wz[a[i]]=i;
    fo(i,1,top) sum[i]=sum[i-1]+f[a[i]][1];
    fo(i,1,top) k+=f[a[i]][0];
    t=h[x];
    while (t){
        if (go[t]!=y){
            l=0;r=top;
            while (l<r){
                mid=(l+r)/2;
                j=sum[mid];
                if (wz[go[t]]<=mid) j-=f[go[t]][1];
                if (sum[top]-f[go[t]][1]+1-j<=n/2) r=mid;else l=mid+1;
            }
            g[go[t]][0]=k-f[go[t]][0]+l;
            if (wz[go[t]]<=l) g[go[t]][0]--;
            j=sum[l];
            if (wz[go[t]]<=l) j-=f[go[t]][1];
            g[go[t]][1]=sum[top]-f[go[t]][1]+1-j;
        }
        t=next[t];
    }
    t=h[x];
    while (t){
        if (go[t]!=y) dg(go[t],x);
        t=next[t];
    }
}
void write(int x){
    if (!x){
        putchar('0');
        putchar('\n');
        return;
    }
    top=0;
    while (x){
        sta[++top]=x%10;
        x/=10;
    }
    while (top) putchar('0'+sta[top--]);
    putchar('\n');
}
int main(){
    freopen("flea.in","r",stdin);freopen("flea.out","w",stdout);
    n=read();
    fo(i,1,n-1){
        j=read();k=read();
        add(j,k);add(k,j);
    }
    dfs(1,0);
    dg(1,0);
    fo(i,1,n){
        ans=0;
        t=h[i];
        while (t){
            if (go[t]!=father[i]) ans+=f[go[t]][0];
            t=next[t];
        }
        ans+=g[i][0];
        write(ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值