1. 程式人生 > >@NOIP2018 - [email protected] 賽道修建

@NOIP2018 - [email protected] 賽道修建

目錄

【部落格搬運 * 3】


@題目描述@

C 城將要舉辦一系列的賽車比賽。在比賽前,需要在城內修建 m 條賽道。

C 城一共有 n 個路口,這些路口編號為 1,2,…,n,有 n−1 條適合於修建賽道的雙向通行的道路,每條道路連線著兩個路口。其中,第 ii 條道路連線的兩個路口編號為 ai 和 bi,該道路的長度為 li 。藉助這 n-1 條道路,從任何一個路口出發都能到達其他所有的路口。

一條賽道是一組互不相同的道路 e1,e2,…,ek,滿足可以從某個路口出發,依次經過 道路 e1,e2,…,ek(每條道路經過一次,不允許調頭)到達另一個路口。一條賽道的長度等於經過的各道路的長度之和。為保證安全,要求每條道路至多被一條賽道經過。

目前賽道修建的方案尚未確定。你的任務是設計一種賽道修建的方案,使得修建的 m 條賽道中長度最小的賽道長度最大(即 m 條賽道中最短賽道的長度儘可能大)

輸入
輸入檔案第一行包含兩個由空格分隔的正整數 n,m,分別表示路口數及需要修建的賽道數。
接下來 n-1 行,第 i 行包含三個正整數 ai,bi,li,表示第 i 條適合於修建賽道的道 路連線的兩個路口編號及道路長度。保證任意兩個路口均可通過這 n-1 條道路相互到達。每行中相鄰兩數之間均由一個空格分隔。

輸出
輸出共一行,包含一個整數,表示長度最小的賽道長度的最大值。

輸入樣例#1
7 1
1 2 10
1 3 5
2 4 9
2 5 8
3 6 6
3 7 7
輸出樣例#1


31

輸入樣例#2
9 3
1 2 6
2 3 3
3 4 5
4 5 10
6 2 4
7 2 9
8 4 7
9 4 4
輸出樣例#2
15

輸入輸出樣例 1 說明
所有路口及適合於修建賽道的道路如下圖所示:樣例解釋1圖
道路旁括號內的數字表示道路的編號,非括號內的數字表示道路長度。 需要修建 1 條賽道。可以修建經過第 3,1,2,6 條道路的賽道(從路口 4 到路口 7), 則該賽道的長度為 9 + 10 + 5 + 7 = 31,為所有方案中的最大值。

輸入輸出樣例 2 說明
所有路口及適合於修建賽道的道路如下圖所示:
樣例解釋2圖
需要修建 3 條賽道。可以修建如下 3 條賽道:
經過第 1,6 條道路的賽道(從路口 1 到路口 7),長度為 6 + 9 = 15;
經過第 5,2,3,8 條道路的賽道(從路口 6 到路口 9),長度為 4 + 3 + 5 + 4 = 16;
經過第 7,4 條道路的賽道(從路口 8 到路口 5),長度為 7 + 10 = 17。
長度最小的賽道長度為 15,為所有方案中的最大值。

資料規模與約定
對於所有的資料, 2 ≤ n ≤ 50,000,1 ≤ m ≤ n-1,1 ≤ ai,bi ≤ n,1 ≤ li ≤ 10,000。

@題解@

首先:看到“使得最小值最大”,一定要聯想到二分答案。
考慮已經固定了最小長度 ≥ x 過後我們應該怎麼求解鏈的最大個數。

令 f(u) 表示以 u 為根的子樹剖分成長度 ≥ x 的最多的鏈個數。再令 g(u) 表示滿足 f(u) 最大的情況下,從 u 的某個子孫到達 u 的最長鏈的長度。

為什麼要先滿足 f(u) 最大呢?假如犧牲 f(u) 使得 g(u) 變大,則 g(u) 對總答案的貢獻最多為 1。假如 f(u) 減少 1,總答案不變;否則總答案還要變小。

怎麼求解這兩個值呢?對於某一個 u,實際上就是相當於這樣一個問題:
給你若干數,讓你兩兩配對,使得滿足兩個數之和 ≥ x 的配對數儘量多。
這裡的若干個數就是 g(u的兒子) + u到該兒子的邊長。

這個問題又怎麼做呢?因為我們既要求解出 f 的最大值,但也要最大化 g。所以我們儘量先配對掉數值小的。
考慮那個最小的數:如果最大的數不能和這個最小的數配對,就直接更新 g(u) 然後跳過;否則求出能和這個最小的數配對的數 p,刪掉 p(視為它已經和最小數匹配),然後 f(u)++。

注意我們還要處理:如果某一個數已經 ≥ x 了,就直接 f(u)++,並刪除這個數。
為什麼這樣是對的呢?如果鏈(a, b)是由鏈(a, c) + 鏈(c, b)組成,且鏈(a, c)的長度已經 ≥ x。則:如果我們把鏈(a, b)替換成鏈(a, c) + 鏈(c, b),答案不會變小。

@程式碼@

刪除這個操作我是使用的並查集來做的。
程式碼實現的細節可能比較多……?大家如果看不懂可以留言在下面問我 qwq 。
對了,程式碼因為是在考場上寫的,所以程式碼中的變數可能和上面所說的不大一樣,還請大家見諒。

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 50000;
const int INF = int(1E9);
struct edge{
    int to, len;
    edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v, int w) {
    edge *p = (++ecnt);
    p->to = v, p->len = w;
    p->nxt = adj[u], adj[u] = p;
    p = (++ecnt);
    p->to = u, p->len = w;
    p->nxt = adj[v], adj[v] = p;
}
int cnt, fa[MAXN + 5], tot[MAXN + 5];
int mid, f[MAXN + 5], g[MAXN + 5];
int find(int x) {
    return fa[x] = (fa[x] == x) ? x : find(fa[x]);
}
void solve(int x) {
    sort(f+1, f+cnt+1);
    for(int i=1;i<=cnt+1;i++)
        fa[i] = i;
    for(int i=cnt;i>=1;i--)
        if( f[i] >= mid )
            fa[i] = i+1, tot[x]++;
    int pos = cnt;
    for(int i=1;i<=cnt;i++) {
        if( fa[i] != i ) continue;
        while( pos > 0 && f[pos] + f[i] >= mid )
            pos--;
        pos = max(pos, i);
        int p = find(pos+1);
        if( p == cnt+1 )
            g[x] = max(g[x], f[i]);
        else fa[p] = p+1, tot[x]++;
    }
}
void dfs(int rt, int pre) {
    tot[rt] = 0; g[rt] = 0;
    for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
        if( p->to == pre ) continue;
        dfs(p->to, rt); tot[rt] += tot[p->to];
    }
    cnt = 0;
    for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
        if( p->to == pre ) continue;
        f[++cnt] = g[p->to] + p->len;
    }
    solve(rt);
}
bool check(int m) {
    dfs(1, 0);
    return tot[1] >= m;
}
int main() {
    int n, m, le = INF, ri = 0;
    scanf("%d%d", &n, &m);
    for(int i=1;i<n;i++) {
        int a, b, l;
        scanf("%d%d%d", &a, &b, &l);
        addedge(a, b, l); ri += l; le = min(le, l);
    }
    while( le < ri ) {
        mid = (le + ri + 1) >> 1;
        if( check(m) ) le = mid;
        else ri = mid - 1;
    }
    printf("%d\n", le);
    return 0;
}