暑假集训 2019.08.01 chemistry

社会主义新天地 提交于 2019-11-25 23:53:59

题意:

给一棵 $n$ 个点的树 , 有$m$次询问. 每次询问给出$x$, 求一个最小的$k$使得把树上$k$条边断掉然后重新连$k$条边形成一棵新的树之后, 树的重心为$x$.
$ $

树的重心:对于一棵无根树上的每个节点 , 将其变成根之后 , 最大子树大小最小的节点便是重心 .

$ $

题解:

首先要知道重心的一个基本性质:若$x$是重心 , 那么以$x$为根的每棵子树的$size$都小于等于$\frac{n}{2}$(挺显然的?.

我们对于根节点 , 考虑最少删掉根节点的几棵子树 , 使得根节点剩余的$size$小于等于$\frac{n}{2}$ .

若按照子树大小排序后删去前$k$大棵子树之后满足$size$小于等于$\frac{n}{2}$ , 对于还在根节点联通块内的每个节点 , 要求的次数等于$k$或$k-1$ . 将某个节点变成根之后 , 其最大子树的$size$肯定是其之前所处的联通块内节点数减去它之前的所有子树的$size$ , 所以我们要用算出联通块中节点数量 . 又因为联通块中节点数量必定满足小于等于$\frac{n}{2}$ , 所以就是断掉$k$条边即可 ; 因为我们还要用联通块中节点数量减去它的其他子树 , 所以可能只用删$k-1$棵子树就满足联通块中节点数量减去它的其他子树后小于等于$\frac{n}{2}$ , 于是乎答案就为$k$或$k-1$ .

对于在删去子树中的节点 , 我们想象一下删去之后把它所在的子树接在根节点再把它变为根 , 就和上面的操作一样了 .

代码:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N=1e6+5;
int n,m,tot,rt,temp,minn=INF,sum,cx;
int head[N],s[N],ma_s[N],son[N],ans[N];
struct Edge{
    int next,to;
}e[N<<1];
inline void add_edge(int from,int to){
    e[++tot].next=head[from];
    e[tot].to=to;
    head[from]=tot;
}
void dfs1(int now,int fa){
    s[now]=1;
    for(int i=head[now];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa) continue;
        dfs1(v,now);
        s[now]+=s[v];
        ma_s[now]=max(ma_s[now],s[v]);
    }
    ma_s[now]=max(ma_s[now],n-s[now]);//因为是随便取的根所以对于节点上面的size也有可能是它的子树
}
bool cmp(int x,int y){return s[x]>s[y];}
void dfs2(int now,int fa,int size){
    ans[now]=cx-(((n-s[now]-size)<<1)<=n);
    for(int i=head[now];i;i=e[i].next)
        if(e[i].to!=fa) dfs2(e[i].to,now,size);
}
int main(){
    freopen("chemistry.in","r",stdin);
    freopen("chemistry.out","w",stdout);
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        add_edge(x,y);
        add_edge(y,x);
    }
    dfs1(1,0);//预处理
    for(int i=1;i<=n;i++)
        if(ma_s[i]<minn) rt=i,minn=ma_s[i];//找出原树重心
    for(int i=head[rt];i;i=e[i].next) son[++temp]=e[i].to;//求出原重心的所有距离为1的儿子
    dfs1(rt,0);//把原重心变为根
    sort(son+1,son+1+temp,cmp);//按子树大小排序
    for(int i=1;i<=temp;i++){
        sum+=s[son[i]];
        if(((sum+1)<<1)>n){
            cx=i;
            break;//加上自身后size*2大于n就跳出
        }
    }
    for(int i=1;i<=temp;i++) dfs2(son[i],rt,sum-max(s[son[i]],s[son[cx]]));//处理答案
    for(int i=1;i<=m;i++){
        scanf("%d",&x);
        printf("%d\n",ans[x]);//查询
    }
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!