樹形結構 —— 樹與二叉樹 —— 樹的中心

【概述】

樹的中心問題是指:當給出 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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章