1. 程式人生 > 實用技巧 >Codeforces Round #670 (Div. 2)/1406C Link Cut Centroids

Codeforces Round #670 (Div. 2)/1406C Link Cut Centroids

題目連結:https://codeforces.com/contest/1406/problem/C

題目大意:對一棵樹刪去一條邊,加上一條邊,使樹只保留一個重心

題目思路:樹的重心是:刪除這個點後最大連通塊的結點數最小。以樣例為例:

刪除點1,點3為一個連通塊,結點數為1,點2,4,5,為一個連通塊,結點數為3 刪除點2,點1,4,6分別為一個連通塊,結點都是1 刪除點3,點1,2,4,5為一個連通塊,結點為4…………………… 這些點中,只有刪除點2,才能使最大連通塊的結點數最小,所以2為該樹重心

如上步驟,發現點2,點3都是重心,這裡需要知道樹最多有2個重心

如果只有一個重心,那便刪除、加上重心的任意一條邊(刪除加上是同一條),否則刪除第2個重心的任意一條邊(不能是與第1個重心相連的邊,否則樹還是有2個重心),然後將該邊與第1個重心相連

接下來就是如何求重心了,我們知道樹的重心是:刪除這個點後最大連通塊的結點數最小,所以我們對每個點求刪除該點後最大聯通塊的最小值,首先通過dfs遞迴求出該點每個子樹的最大連通塊的數量,(同時對該點所有結點數求和,即1 + 該點所有子樹的結點數,1為該點),然後與 `n - sum` 比較,求出該點的最大連通塊的值

AC程式碼:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#include <string>
#include <cstring>
#include <set>
#include <stack>
#include <deque>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int N = 1e6 + 10, M = 1e6 + 10;
const int base = 1e9;
const int P = 131;
const int MAX = 1e5;
int n, m, min_v = INF;
int h[N], e[M], ne[M], idx;
int son[N], siz[N];
bool st[N];
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u)
{
    st[u] = true;
    int sum = 1, res = 0;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])//子節點未遍歷過才加
        {
            siz[j] = dfs(j);//求子樹的結點數
            res = max(res, siz[j]);//求子樹中最大連通塊的數量
            sum += siz[j];//對該點的結點求和
        }
    }
    res = max(res, n - sum);//n-sum為除掉子樹的連通塊的數量
    son[u] = res;
    min_v = min(min_v, son[u]);//計算最大聯通塊的最小值
    return sum;//返回子樹的結點的和
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        idx = 0, min_v = INF;
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i)
            h[i] = -1;
        for (int i = 1; i <= n; ++i)
            st[i] = 0;
        for (int i = 1; i <= n - 1; ++i)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            add(a, b), add(b, a);
        }
        dfs(1);
        int pos = 0, x1 = 0, x2 = 0;
        for (int i = 1; i <= n; ++i)//遍歷找重心
        {
            if (son[i] == min_v)
            {
                if (x1 == 0)
                {
                    x1 = i;
                    ++pos;
                }
                else if (x2 == 0)
                {
                    x2 = i;
                    ++pos;
                    break;
                }
            }
        }
        if (pos == 1)//一個重心
        {
            int j = e[h[x1]];
            printf("%d %d\n", x1, j);
            printf("%d %d\n", x1, j);
        }
        else//兩個重心
        {
            int j = 0;
            for (int i = h[x2]; i != -1; i = ne[i])
            {
                if (e[i] != x1)//不能是第1,2重心相連的邊
                    j = e[i];
            }
            printf("%d %d\n", x2, j);
            printf("%d %d\n", x1, j);
        }
    }
    return 0;
}