树形结构 —— 树与二叉树 —— 树的中心

【概述】

树的中心问题是指:当给出 n 个结点与 n-1 条边后,要选定一个点作为整棵树的根结点,使得从该点到每个叶结点的最长路径最短。

树的中心问题主要有两种方法:DFS/BFS 进行搜索、树形 DP 进行状态转移

【DFS】

根据树的中心问题的描述,显然可以知道,树的中心一定在树的直径上,而且趋于终点,否则它的最远距离只会更远。

因此,我们在利用 DFS 寻找树的直径的同时,对于直径的两个端点 st、ed,分别求其到每个点的距离 disSt[i]、disEd[i]

最后,对每个点进行更新,求最小距离 minn=min(minn,max(disSt[i],disEd[i])) 即可

struct Edge {
    int to, val;
    int next;
    Edge(){}
    Edge(int to,int val,int next):to(to),val(val),next(next){}
} edge[N];
int n;
int head[N], tot;
int diameter,maxx, id;
int dis[N], disSt[N], disEd[N];
void addEdge(int from, int to, int val) {
    edge[++tot].to = to;
    edge[tot].val = val;
    edge[tot].next = head[from];
    head[from] = tot;
}
void dfs(int x, int father) {
    for (int i = head[x]; i != -1; i = edge[i].next) {
        int y = edge[i].to;
        int val = edge[i].val;
        if (y == father)
            continue;
        dis[y] = dis[x] + val;
        if(dis[y]>maxx){
            maxx = dis[y];
            id = y;
        }
        dfs(y, x);
    }
}
void calcDiameter(){
     //第一遍dfs
    maxx = 0;
    id = 1;
    dfs(1, 0);
    int st = id;
 
    //第二遍dfs
    maxx = 0;
    dis[st] = 0;
    dfs(st, 0);
    int ed = id;
 
    diameter = maxx; //树的直径

    for (int i = 1; i <= n; i++)
        disSt[i] = dis[i];
    dis[ed] = 0;
    dfs(ed, 0);
    for (int i = 1; i <= n; i++)
        disEd[i] = dis[i];
}

int main() {
    scanf("%d", &n);
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n - 1; i++) {
        int x, y, val;
        scanf("%d%d%d", &x, &y, &val);
        addEdge(x, y, val);
        addEdge(y, x, val);
    }

    calcDiameter();

    int pos, minn = INF;
    for (int i = 1; i <= n; ++i) {
        if (minn > max(disSt[i], disEd[i])){
            minn = max(disSt[i], disEd[i]);
            pos = i;
        }
    }
    printf("%d %d\n", pos, minn);
    return 0;
}

【树形 DP】

利用树形 DP 求解时,我们需要维护每个点 i 到所有叶结点的最长距离 up[i],从而去更新树的中心。

由于采用树形 DP 的方法,在求树的直径时已经知道如何维护每个结点 i 到其子树的叶结点的最长距离 dis1[i] 与次长距离 dis2[i],那么接下来就要考虑如何维护这个点向上的最远距离 up[i]

依旧用 pos1[x] 表示 dis1[x] 在哪个点更新,pos2[x] 表示 dis2[x] 在哪个点更新,再求出树的直径后,再次进行一次 DFS 即可

struct Edge {
    int to, val;
    int next;
    Edge(){}
    Edge(int to,int val,int next):to(to),val(val),next(next){}
} edge[N];
int n;
int head[N], tot;
int dis1[N], dis2[N];//分别维护第i个点的最长链、次长链
int pos1[N],pos2[N];//分别维护dis1[i]、dis2[i]从哪个点更新
int up[N];//点i到所有叶结点的最远距离
void addEdge(int from, int to, int val) {
    edge[++tot].to = to;
    edge[tot].val = val;
    edge[tot].next = head[from];
    head[from] = tot;
}
void dfs(int x, int father) {
    for (int i = head[x]; i != -1; i = edge[i].next) {
        int y = edge[i].to;
        int val = edge[i].val;
        if (y == father)
            continue;
        dfs(y, x);
        if (dis1[y] + val > dis1[x]) {
            dis2[x] = dis1[x];
            dis1[x] = dis1[y] + val;
            pos2[x] = pos1[x];
            pos1[x] = y;
        } 
        else if (dis1[y] + val > dis2[x]) {
            dis2[x] = dis1[y] + val;
            pos2[x] = y;
        }
    }
}
void dfsCenter(int x, int father) {
    for (int i = head[x]; i != -1; i = edge[i].next) {
        int y = edge[i].to;
        int val = edge[i].val;
        if (y == father)
            continue;
        if (pos1[x] != y)
            up[y] = max(dis1[x], up[x]) + val;
        else
            up[y] = max(dis2[x], up[x]) + val;
        dfsCenter(y, x);
    }
}
int main() {
    scanf("%d", &n);
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n - 1; i++) {
        int x, y, val;
        scanf("%d%d%d", &x, &y, &val);
        addEdge(x, y, val);
        addEdge(y, x, val);
    }
    dfs(1, 0);
    dfsCenter(1, 0);

    int pos, minn = INF;
    for (int i = 1; i <= n; i++) {
        if (minn > max(up[i], dis1[i])) {
            minn = max(up[i], dis1[i]);
            pos = i;
        }
    }
    printf("%d %d", pos, minn);
    return 0;
}

【变形问题】

树的中心问题,最常见的一种变型问题是:给出一棵树 n 个点的点权与 n-1 条边的边权,求树的最小代价的和,定义代价为树中两点距离乘以点的点权

该问题是最常见的,一般数据规模较小,利用 Floyd 算法即可解决。

int n;
int G[N][N], node[N], sum[N];

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) //点权
        cin >> node[i];
    for (int i = 1; i <= n - 1; i++) { //边权
        int x, y, dis;
        cin >> x >> y >> dis;
        G[x][y] = dis;
        G[y][x] = dis;
    }

    //Floyd记录各点间的距离
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (G[i][j] > G[i][k] + G[k][j])
                    G[i][j] = G[i][k] + G[k][j];

    //枚举求最小代价
    int minn = INF;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++)
            sum[i] += G[i][j] * node[j];
        if (sum[i] < minn)
            minn = sum[i];
    }
    cout << minn << endl;
    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值