树的重心

树的重心

分析:

就是在深搜的时候记录这个点的两边子节点的最大值

如果小于等于这个树上的点的数量的一半,就是重心。

代码长这样:

void dfs1(int x,int fa){
    sizes[x]=wei[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i]; 
        if(y==fa) continue;
        dfs1(y,x); 
        sizes[x]+=sizes[y];
        if(wei[x]<sizes[y]) wei[x]=sizes[y];
    }
    wei[x]=max(wei[x],n-sizes[x]);
    if(wei[x]<=n/2) zhongxin=x;
}

例题:

题意

给定一棵节点数为 \(n\) 的树 , 删一条边然后加上一条边 , 使得该树的重心唯一 。(删掉的边和加上的边可以是同一条)

分析

如果只有一个重心,咋删都行

如果有两个重心,我们思考一下怎么才能变成一个重心

假如重心为 \(a1\) ,\(a2\) ,两者不算对方子树时,子树大小是相等的。

当子树大小不相等时,就只有一个重心。

因此可以断掉重心和连接点的一条边,将这条边连接到另一棵字数上,子树大小肯定不相等。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;

int T,n;
int sizes[N],wei[N],q[4];
int nxt[N],ver[N],tot,head[N];
int cnt,ans1,ans2,minn;

void init(){
    for(int i=0;i<=n;i++) sizes[i]=head[i]=wei[i]=0;
    cnt=ans1=ans2=tot=0;
    minn=0x3f3f3f3f;
}

void add(int x,int y){
    ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
}

void dfs1(int x,int fa){
    sizes[x]=wei[x]=1;
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i]; 
        if(y==fa) continue;
        dfs1(y,x); 
        sizes[x]+=sizes[y];
        if(wei[x]<sizes[y]) wei[x]=sizes[y];
    }
    wei[x]=max(wei[x],n-sizes[x]);
    if(wei[x]<minn) minn=wei[x];
}

int dfs2(int x,int fa){
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i]; if(y==fa) continue;
        ans2=x; return dfs2(y,x);
    }
    return x;
}

int main(){
    cin>>T;
    while(T--){
        scanf("%d",&n); init();
        for(int i=1,x,y;i<n;i++){
            scanf("%d%d",&x,&y);
            add(x,y); add(y,x);
        }
        dfs1(1,0);
        // for(int i=1;i<=n;i++) cout<<wei[i]<<" ";puts("");
        for(int i=1;i<=n;i++) if(wei[i]==minn) q[++cnt]=i;
        if(cnt==1) printf("%d %d\n%d %d\n",q[1],ver[head[q[1]]],q[1],ver[head[q[1]]]);
        else{
            int ans1=dfs2(q[2],q[1]);
            printf("%d %d\n%d %d\n",ans2,ans1,q[1],ans1);
        }
    }
    system("pause");
    return 0;
}

P1364 医院设置

题意
现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻接点之间的距离为 \(1\)

节点给定居民人数。

分析

我们可以像树形 \(dp\) 一样转移状态:

首先,算出来每个节点作为 \(1\) 的子树,对应的子树人数大小。

我们可以据此算出来 节点 \(1\) 的答案大小

接着,过节点 \(1\) 的答案进行转移:

设从 \(x->y\) ,则左侧增加 \(sum-sizes[y]\) 的答案(就是以 \(x\) 为根,向 \(y\) 相反方向的子树大小),右侧减少 \(sizes[y]\) 的答案,加上 \(ans[x]\) 就是 \(ans[y]\)

最后输出最小值即可。

代码

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e3+5;
int nxt[N],ver[N],tot,head[N];
int val[N],sizes[N],peo[N];
int n,sum,root,ans=0x3f3f3f3f;
void add(int x,int y){
    ver[++tot]=y;nxt[tot]=head[x]; head[x]=tot;
}
void dfs1(int x,int fa,int dep){
    sizes[x]=val[x];
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa) continue;
        dfs1(y,x,dep+1);
        sizes[x]+=sizes[y];
        peo[x]=max(peo[x],sizes[y]);
    }
    peo[1]+=val[x]*dep;
}
void dfs2(int x,int fa){
    for(int i=head[x];i;i=nxt[i]){
        int y=ver[i];
        if(y==fa) continue;
        peo[y]=peo[x]+sizes[1]-sizes[y]*2;
        // = peo[x] +sizes[x]-sizes[y] 因为 sizes[x]=sum-sizes[y],所以就这样了
        dfs2(y,x);
    }
    ans=min(ans,peo[x]);
}
signed main(){
    cin>>n;
    for(int i=1,z,x,y;i<=n;i++){
        scanf("%lld%lld%lld",&val[i],&x,&y);
        if(x) add(x,i),add(i,x);
        if(y) add(y,i),add(i,y); 
    }
    dfs1(1,0,0);
    dfs2(1,0);
    cout<<ans<<endl;
    system("pause");
    return 0;
}
posted @ 2021-09-28 20:04  Evitagen  阅读(49)  评论(0编辑  收藏  举报