「題解」NOIP2015 提高組 運輸計劃
阿新 • • 發佈:2021-07-27
Problem
題意:
給定一棵帶邊權的樹,以及若干條樹上的路徑。我們可以使一條樹邊的邊權變為 \(0\) ,求變化後最長路徑的最小值。
Solution
將題目簡化過後,從 “求最長路徑的最小值” 可以大概猜到這道題需要用到二分。這道題中的完成時間很明顯具有單調性:
- 若 \(t\) 恰好滿足題意,則 \({\forall} t_0 < t\) 都不滿足,\({\forall}t_1 > t\) 都滿足。\(\\\)
所以這道題需要來二分答案。
接下來就要思考:對於一個完成時間 \(t\) (最長路徑的最小值),我們該如何判斷是否可行呢?
我們可以先用 \(\operatorname{LCA}\)
對於長度小於 \(t\) 的路徑是一定滿足的,所以我們只需要考慮長度大於 \(t\) 的路徑。
這時候貪心一下:使什麼樣的邊邊權為 \(0\) 可以使所有長度大於 \(t\) 的路徑都最大程度的變小。
因為每條都需要變小,所以一定要選它們公共的邊,而又要最大程度變小,所以一定要選這其中邊權最大的邊。
若有 \(k\) 條路徑的長度都大於 \(t\) ,則我們想要選擇改變的公共邊一定被路徑覆蓋了 \(k\) 次(畢竟是公共的)。對於維護覆蓋次數,我們可以採用樹上差分。
最後,如果所有長度大於 \(t\) 的路徑減去找到的路徑長度(沒找到的話顯然不行)都小於了 \(t\)
細節詳見程式碼。。。
Code
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int Maxn = 3e5; int n, m, maxdis; int vis[Maxn + 5], fa[Maxn + 5], dep[Maxn + 5], flag[Maxn + 5], f[Maxn + 5], cnt[Maxn + 5]; struct Node { int v, w; } ; struct Plan { int u, v, dis, lca; friend bool operator < (Plan x, Plan y) { return x.dis < y.dis; } } s[Maxn + 5]; vector < Node > ask[Maxn + 5]; vector < Node > edge[Maxn + 5]; int Max (int a, int b) { return a > b ? a : b; } void Make_Set () { for (int i = 1; i <= n; i ++) fa[i] = i; } int Find_Set (int u) { return u == fa[u] ? u : fa[u] = Find_Set (fa[u]); } void Tarjan (int u) { vis[u] = 1; for (auto x : edge[u]) { if (vis[x.v]) continue; f[x.v] = u; dep[x.v] = dep[u] + x.w; Tarjan (x.v); fa[x.v] = u; } for (auto x : ask[u]) { if (vis[x.v]) s[x.w].lca = Find_Set (x.v); } } void Dfs (int u, int tar, int dis) { cnt[u] = flag[u]; for (auto x : edge[u]) { if (x.v == f[u]) continue; Dfs (x.v, tar, x.w); cnt[u] += cnt[x.v]; //將子樹標記向上傳 } if (cnt[u] == tar) { maxdis = Max (maxdis, dis); //cnt[u]==tar表示 u->fa[u]這條邊被覆蓋了 tar 次 } } bool Check (int mid) { int ct = 0; memset (cnt, 0, sizeof cnt); memset (flag, 0, sizeof flag); for (int i = 1; i <= m; i ++) { if (s[i].dis <= mid) { break; } ct ++; flag[s[i].u] ++; flag[s[i].v] ++; flag[s[i].lca] -= 2; //樹上差分 } if (!ct) return true; maxdis = 0; Dfs (s[1].lca, ct, 0); //找最大公共邊,直接從最長路徑的 LCA為根找公共邊即可 return s[1].dis - maxdis <= mid; //最長路徑滿足則所有都滿足 } int main () { scanf ("%d %d", &n, &m); for (int i = 1; i < n; i ++) { int u, v, w; scanf ("%d %d %d", &u, &v, &w); edge[u].push_back ({v, w}); edge[v].push_back ({u, w}); } for (int i = 1; i <= m; i ++) { scanf ("%d %d", &s[i].u, &s[i].v); ask[s[i].u].push_back ({s[i].v, i}); ask[s[i].v].push_back ({s[i].u, i}); } Make_Set (); Tarjan (1); //Tarjan離線求LCA for (int i = 1; i <= m; i ++) { s[i].dis = dep[s[i].u] + dep[s[i].v] - dep[s[i].lca] * 2; //LCA求路徑長 } sort (s + 1, s + 1 + n); reverse (s + 1, s + 1 + n); //先從大到小排序 int l = 0, r = s[1].dis; while (l < r) { int mid = l + r >> 1; if (Check (mid)) r = mid; else l = mid + 1; } printf ("%d", l); return 0; }