树的换根(重心)

题目链接:
https://ac.nowcoder.com/acm/contest/4479/C
思想:树的换根
在一个树中,首先任意选一个点作为根开始深搜,计算每个子树的节点数量,和这个根节点的深度和,之后从邻接点依次转移,取最小值。
树根转换公式:dp[邻接点深度和]=dp[该点深度和]-num[该点为根的子树的节点数量]+(n-num[ ]);
公式来源:一个树的所有儿子节点的深度和可以通过连接他们的边转移。
深刻理解:最小的深度和一定是来自某次深搜中的,求最小,就要让num[ ]最大,也就是儿子节点为根的树节点数量最多,也就是num[ ]-1;转移关系,u为第一次的根,v1,v2…为u的儿子,之后v为根,其最大子树为num[ v ]-1,之后当找出最大的子树时(节点数量最大的子树),与另一半树作比较,(去掉v之后的所有子树找最大的),之后所有节点中取最大子树最小的,就可以求出最小深度和了,因为子树越小(写不下去了!)因为深度和可以通过找树的重心来找,深刻理解的结论就是树的重心,结点深度和最小!!

树的重心:

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#define max(x,y) (x>y?x:y)
const int N=1e6+1;
int n;
vector<int> G[N];
int son[N];//s[v]:表示以结点v为根的子树的结点个数
bool vis[N];
int res=N<<1,tu;
void dfs(int v,int par){
    son[v]=1;
    int ans=-1;
    for(int i=0;i<G[v].size();++i){
        int u=G[v][i];
        if(par==u) continue;
        dfs(u,v);
        son[v]+=son[u];
        ans=max(ans,son[u]);//找出结点v拥有的所有子树中
        //最大子树的结点个数
    }
    ans=max(ans,n-son[v]);//去掉v后,能产生的最大子树的结点数 :
    //要么在v的子树中产生,要么在另一半树中产生(max=n-son[v],
    //即:最大等于n减去v子树的结点总数)
    if(ans<res) res=ans,tu=v;
}
//计算深度和
void dfsw(int v,int k){
    vis[v]=1;
    for(int i=0;i<G[v].size();++i){
        int u=G[v][i];
        if(vis[u]) continue;
        res+=k;
        dfsw(u,k+1);
    }
}
void solve(){
    memset(son,0,sizeof(son));
    dfs(1,-1);
    res=0;
    memset(vis,0,sizeof(vis));
    dfsw(tu,1);
    cout<<res;
}
int main(){
    cin>>n;
    for(int i=0;i<n-1;++i){
        int a,b;
        cin>>a>>b;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    solve();
    return 0;
}

深搜加动态规划维护最优解:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e6+7;
int head[maxn],dep[maxn],p[maxn],vis[maxn],dp[maxn],cnt,n;
struct edge{
    int to,next;
}e[maxn<<1];
void add(int x,int y){e[++cnt]=edge{y,head[x]},head[x]=cnt;}
int dfs(int x){
    vis[x]=1,p[x]=1;
    for(int i=head[x];i;i=e[i].next){
        int chi=e[i].to;
        if(!vis[chi]){
            p[x]+=dfs(chi);
            dep[x]+=dep[chi]+p[chi];
            //dfs由底及上,最底下的结点深度为(dep=0)
            //最底层节点的父亲深度为(dep=0)+(p[子结点]=1)=1
            //之后逐渐+1
        }
    }
    return p[x];
}
void solve(int x){
    vis[x]=1;
    for(int i=head[x];i;i=e[i].next){
        int chi=e[i].to;
        if(!vis[chi]){
            dp[chi]=dp[x]-2*p[chi]+n;
            solve(chi);
        }
    }
}
int main(){
    int x,y,m=0x3f3f3f3f;
    scanf("%d",&n);
    for(int i=1; i<n; i++){
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(1);
    memset(vis,0,sizeof(vis));
    dp[1]=dep[1];
    solve(1);
    for(int i=1; i<=n; i++){
        m=min(m,dp[i]);
    }
    printf("%d\n",m);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值