4.7 简单树上问题

简单树上问题

1. 树的重心

重心的定义:以某一节点为根,如果该节点的最大子树的节点最小,则u为树的重心(也就是子树最均衡)

const int N = 50005;          //最大结点数
struct Edge{ int to, next;} edge[N<<1];      //两倍:u-v, v-u
int head[N], cnt = 0;
void init(){                  //链式前向星:初始化
    for(int i=0; i<N; ++i){
        edge[i].next = -1; 
        head[i] = -1;
    }
    cnt = 0;
}
void addedge(int u,int v){    //链式前向星:加边u-v
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
int n;
int d[N], ans[N], num=0, maxnum=1e9;        //d[u]: 以u为根的子树的结点数量
void dfs(int u,int fa){                        
    d[u] = 1;                               //递归到最底层时,结点数加1
    int tmp = 0;
    for(int i=head[u]; ~i; i=edge[i].next){ //遍历u的子结点。~i也可以写成i!=-1
        int v = edge[i].to;                 //v是一个子结点
        if(v == fa) continue;               //不递归父亲
        dfs(v,u);                           //递归子结点,计算v这个子树的结点数量
        d[u] += d[v];                       //计算以u为根的结点数量
        tmp = max(tmp,d[v]);                //记录u的最大子树的结点数量
    }
    tmp = max(tmp, n-d[u]);                 //tmp = u的最大连通块的结点数
    //以上计算出了u的最大连通块
    //下面统计疑似教父。如果一个结点的最大连通块比其他结点的都小,它是疑似教父
    if(tmp < maxnum){                       //一个疑似教父
        maxnum = tmp;                       //更新“最小的”最大连通块 
        num = 0;
        ans[++num] = u;                     //把教父记录在第1个位置
    }
    else if(tmp == maxnum)  ans[++num] = u; //疑似教父有多个,记录在后面            
}
int main(){
    scanf("%d",&n);
    init();
    for(int i=1; i<n; i++){
        int u, v;      scanf("%d %d", &u, &v);
        addedge(u,v);  addedge(v,u);
    }
    dfs(1,0);
    sort(ans+1, ans+1+num);
    for(int i=1;i<=num;i++)   printf("%d ",ans[i]);
}

2. 树的直径

树的直径是树上最远的两点间的距离,又被称为树的最远点对。

1. 两次DFS
  1. 需要完整的路径
  2. 不可以有负权边
const int N=1e5+10;
struct edge{ int to,w;};    //to: 边的终点  w:权值
vector<edge> e[N];          //用邻接表存边
int dist[N];                //记录距离
void dfs(int u,int father,int d){   //用dfs计算从u到每个子结点的距离
    dist[u]=d;
    for(int i=0;i<e[u].size();i++)
        if(e[u][i].to != father)    //很关键,这一句保证不回头搜父结点
            dfs(e[u][i].to, u, d + e[u][i].w);   
}
int main(void){
    int n;   cin>>n;
    for(int i=0;i<n-1;i++){
        int a,b,w; cin>>a>>b>>w;
        e[a].push_back({b,w});       //a的邻居是b,边长w
        e[b].push_back({a,w});       //b的邻居是a
    }
    dfs(1,-1,0);                     //计算从任意点(这里用1号点)到树上每个结点的距离
    int s = 1;
    for(int i=1;i<=n;i++)            //找最远的结点s, s是直径的一个端点
        if(dist[i]>dist[s])  s = i;
    dfs(s,-1,0);                     //从s出发,计算以s为起点,到树上每个结点的距离
    int t = 1;
    for(int i=1;i<=n;i++)            //找直径的另一个端点t
        if(dist[i]>dist[t])  t = i;
    cout << dist[t]<<endl;           //打印树的直径的长度
    return 0;
}

2. 树形DP
  1. 只可以求长度

  2. 可以求负权边

const int N=1e5+10;
struct edge{int to,w; };           //to: 边的终点  w:权值
vector<edge> e[N];
int dp[N];
int maxlen = 0;
bool vis[N];
void dfs(int u){
    vis[u] = true;
    for(int i = 0; i < e[u].size(); ++ i){
        int v = e[u][i].to,  edge = e[u][i].w;
        if(vis[v])   continue;     //v已经算过
        dfs(v);
        maxlen = max(maxlen, dp[u]+ dp[v]+ edge);  
               //计算max{f[u]}。注意此时dp[u]不包括v这棵子树,下一行才包括
        dp[u] = max(dp[u], dp[v] + edge); //计算dp[u],此时包括了v这棵子树
    }
    return ;
}
int main(){
    int n;    cin >> n;
    for(int i = 0; i < n-1; i++){
        int a, b, w;     cin >> a >> b >> w;
        e[a].push_back({b,w});  //a的邻居是b,路的长度w
        e[b].push_back({a,w});  //b的邻居是a
    }
    dfs(1);                     //从点1开始DFS
    cout << maxle
        n << endl;
    return 0;
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值