1. 程式人生 > 實用技巧 >HDU-6881 Tree Cutting (HDU多校D10T5 點分治)

HDU-6881 Tree Cutting (HDU多校D10T5 點分治)

HDU-6881 Tree Cutting

題意

\(n\) 個點的一棵樹,要求刪除儘量少的點,使得刪點之後還是一棵樹,並且直徑不超過 \(k\),求刪除點的數量

分析

補題之前的一些錯誤想法:

  1. 嘗試將某條直徑拎出來,然後貪心的找可以保留下來的點的最大個數(沒辦法保證刪點之後的直徑還在拎出來的那條路徑上,另外如果在該路徑上面尺取大概也不可做)
  2. 樹的所有直徑必然交於一點(當直徑為奇數時可以是邊上的一點)。問題轉換為對於每個點求樹上與其距離 \(\lfloor {k\over 2} \rfloor\) 的點的個數。嘗試樹上DP,或者差分亂搞,只能計算出每個點子樹上的,並不完整,有想過點分治,但是沒有想清楚要如何處理。

開始講正解:

考慮點分治,假設當前處理的根是 \(rt\),那麼很容易算出根的答案,如何計運算元樹內的?假設 \(rt\) 有一顆子樹 \(u\) ,在計算 \(u\) 中的點 \(x\) 的答案時,不需要考慮 \(u\) 中除 \(x\) 外的其他點對它的貢獻,這一部分會繼續分治下去求解。現在只需要計算 \(rt\) 除去 \(u\) 的點對 \(x\) 的貢獻即可。

由於樹邊沒有權值,我們可以用深度 \(dep\) 來表示樹上路徑距離,在求 \(x\) 的答案時,假設直徑為 \(k\) (先假設\(k\)是偶數), 那麼也就是求除去 \(u\) 這顆子樹,其他深度大於 \((k/2) - dep[x]\)

的點的個數。可以先預處理出來 \(rt\) 的深度陣列,然後求解 \(u\) 時,在預處理出來一個深度陣列 \(c\),兩者做差即可。

設子樹 \(u\) 旁邊的子樹 \(v\) 上的一點 \(y\) , 那麼當 \(dep[y] + dep[x] > k/2\), 才需要累計 \(y\)\(x\)的貢獻

\(k\) 為奇數時,樹的直徑中點在邊上,我們考慮 \(x\)\(anc[x]\) 這條邊,答案應該是深度大於 \((k-1)/2-dep[u]+1\)的點的個數。但是如果 \(anc[x]\)\(rt\),要注意還要累計\(x\) 所在子樹 \(u\) 上的貢獻。因為該邊並不會遞迴下去分治求解。

const int N = 300000 + 5;
typedef pair<int,int> pii;
vector<pii> g[N];
int n, k;
int anc[N], sz[N], dep[N], allcount[N], o[N];
int cnt_node[N], cnt_edge[N];
bool del[N];
int rt, min_size, all, max_dep, node_len, edge_len;
void getsz(int x, int fa) {
    anc[x] = fa;
    sz[x] = 1;
    int maxpart = 0;
    for(auto t : g[x]) {
        if(t.first == fa || del[t.first]) continue;
        getsz(t.first, x);
        sz[x] += sz[t.first];
        maxpart = max(maxpart, sz[t.first]);
    }
    maxpart = max(maxpart, all - sz[x]);
    if(maxpart < min_size) {
        min_size = maxpart; rt = x;
    }
}
// 獲得點分治當前處理樹的dep陣列,與字尾和 allcount
void travel(int u) {
    static int o[N];// BFS佇列
    int l = 0, r = -1;
    o[++r] = u;
    while(l <= r) {
        int u = o[l++];
        allcount[dep[u]] ++;
        max_dep = max(max_dep, dep[u]);
        for(auto &v : g[u]){
            if(v.first == anc[u] || del[v.first]) continue;
            anc[v.first] = u;
            dep[v.first] = dep[u] + 1;
            o[++r] = v.first;
        }
    }
    for(int i=max_dep-1;i>=0;i--) allcount[i] += allcount[i+1];
}
void travel2(int u) {
    static int c[N], o[N];
    int l = 0, r = -1;
    o[++r] = u;
    while(l <= r) {
        int u = o[l++];
        c[dep[u]] ++;
        for(auto &v:g[u]) {
            if(del[v.first] || v.first == anc[u]) continue;
            o[++r] = v.first;
        }
    }
    int Max = dep[o[r]];
    for(int i=Max - 1; i >= 0; i--) c[i] += c[i+1];
    for(int i=0;i<=r;i++){
        int u = o[i];
        int d = max(0, node_len - dep[u] + 1);
        cnt_node[u] += allcount[d] - c[d];
        // 下面是處理樹直徑中心在邊上的情況
        d = max(0, edge_len - dep[u] + 2);
        for(auto &v:g[u]) {
            if(v.first != anc[u]) continue;
            cnt_edge[v.second] += allcount[d] - c[d];
            if(anc[u] == rt) { // 如果是與rt相連的邊,要統計 c 的答案
                cnt_edge[v.second] += c[edge_len + 2];
            }
        }
    }
    // 最後要記得清空
    for(int i=0;i<=Max;i++) c[i] = 0;
}
void get(int u) {
    del[u] = 1;
    dep[u] = 0;
    max_dep = 0;
    travel(rt);
    cnt_node[u] += allcount[node_len + 1];
    for(auto &v : g[u]) {
        if(del[v.first]) continue;
        travel2(v.first);
    }
    _rep(i,0,max_dep) allcount[i] = 0;
    for(auto &v : g[u]) {
        if(del[v.first]) continue;
        all = sz[v.first]; min_size = all;
        getsz(v.first, 0);
        getsz(rt, 0);
        get(rt);
    }
}

int main(){
    int T; scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &k);
        node_len = k / 2;
        edge_len = (k - 1) / 2;

        _rep(i, 1, n) {
            g[i].clear();
            cnt_edge[i] = cnt_node[i] = del[i] = 0;
        }
        _rep(i, 1, n-1) {
            int u, v; scanf("%d%d", &u,&v);
            g[u].push_back(pii(v, i));
            g[v].push_back(pii(u, i));
        }
        all = n; min_size = n;
        getsz(1, 0);
        getsz(rt, 0);
        get(rt);
        int res = n;
        _rep(i, 1, n) res = min(res, cnt_node[i]);
        _rep(i, 1, n-1) res = min(res, cnt_edge[i]);
        printf("%d\n", res);
    }
    return 0;
}