P5021賽道修建 題解
阿新 • • 發佈:2021-08-17
理一下思路,這道題我們首先的想法是二分答案一個 $check $ 沒問題吧?
我們二分了一個值,考慮 \(check\) 的過程。
我們每次記錄每個節點還沒被併入的鏈長度,然後實行在樹上進行一個 \(dfs\) 的過程。
然後就是考慮這個 \(dfs\) 的過程中我們每次遍歷完子樹,然後我們其實就將這個裡面劃分成了若干條鏈。
考慮我們當前節點是起的一個什麼作用?
它可以充當鏈頂端以及作為兩個節點的 \(LCA\) 然後進行合併。
考慮如果說一個鏈長度如果加上到當前節點的長度已經滿足大於等於我們設定的 \(lim\) 那麼就很顯然的就可以將它們進行一個合併,然後增大我們的鏈次數。
題解中多數地方寫到我們要考慮最大化合並次數,為什麼?
因為你首先是要滿足的是你能劃分出來大於等於 \(m\) 條鏈,而不是說讓這裡面的鏈長有足夠的大。
這樣你才可以讓你限制的這個 \(lim\) 儘量可能的大。
所以說這是為什麼我們要進行最大化合並次數的原因。
那麼我們考慮充當鏈頂端的方法很簡單,你直接在遍歷子節點的時候 \(check\) 一下就好了。
問題在於充當 \(lca\) 時怎麼辦
這個也好解決。
我們其實可以選一個大的和一個小的然後去 \(check\) 。
然後注意一件事,我們不能貿然的將排序後的拿去左端點右端點匹配。
我們要考慮更高階一點的東西。
我們是要儘量合併最多
所以我們控制的是第一個可以合併的更他進行合併
那麼這個可以利用 \(lowerbound\)
然後我們考慮合併完了之後很明顯的我們要將這個給刪除掉顯然吧
因為不刪除掉就會造成影響吧。
可以用 \(multiset\) 進行實現,但是看了大佬的做法發現可以用一個並查集代替這個過程。
然後就很妙這就是個很牛逼的思路,感覺可以用在一些有趣的地方。
#include <bits/stdc++.h> #define int long long using namespace std; namespace IO { int len = 0; char ibuf[(1 << 20) + 1], *iS, *iT, out[(1 << 25) + 1]; #define gh() \ (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), \ (iS == iT ? EOF : *iS++) : *iS++) inline int read() { char ch = gh(); int x = 0; char t = 0; while (ch < '0' || ch > '9') t |= ch == '-', ch = gh(); while (ch >= '0' && ch <= '9') x = x * 10 + (ch ^ 48), ch = gh(); return t ? -x : x; } inline void putc(char ch) { out[len++] = ch; } template <class T> inline void write(T x) { if (x < 0) putc('-'), x = -x; if (x > 9) write(x / 10); out[len++] = x % 10 + 48; } string getstr(void) { string s = ""; char c = gh(); while (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == EOF) c = gh(); while (!(c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == EOF)) s.push_back(c), c = gh(); return s; } void putstr(string str, int begin = 0, int end = -1) { if (end == -1) end = str.size(); for (int i = begin; i < end; i++) putc(str[i]); return; } inline void flush() { fwrite(out, 1, len, stdout); len = 0; } } // namespace IO using IO::flush; using IO::getstr; using IO::putc; using IO::putstr; using IO::read; using IO::write; #define pr pair<int, int> const int N = 2e5; int n, m, lim, num, fa[N], ans[N], s[N], vis[N]; vector<pr> ver[N]; int getfa(int x) { if (fa[x] != x) fa[x] = getfa(fa[x]); return fa[x]; } void dfs(int x, int ff) { s[x] = 0; vector<int> q; q.clear(); // ok for (int i = 0; i < ver[x].size(); i++) { int to = ver[x][i].first, val = ver[x][i].second; if (to == ff) continue; dfs(to, x); if (s[to] + val >= lim) num++; else q.push_back(s[to] + val); } if (!q.size()) return; if (q.size() == 1) { s[x] = q[0]; return; } sort(q.begin(), q.end()); // ok for (int i = 0; i <= q.size() + 1; i++) vis[i] = 0, fa[i] = i; for (int i = 0; i < q.size() - 1; i++) { if (vis[i]) continue; if (i >= q.size() - 1 || i == -1) break; int t = lower_bound(q.begin() + i + 1, q.end(), lim - q[i]) - q.begin(); if (t >= q.size()) continue; t = getfa(t); if (t >= q.size()) continue; // printf("%lld %lld | %lld %lld \n", i, q[i], t, q[t]); int val = 0; val = q[t] + q[i]; if (val < lim) continue; num++; vis[t] = 1; vis[i] = 1; fa[t] = t + 1; } for (int i = q.size() - 1; i >= 0; i--) { if (!vis[i]) { s[x] = q[i]; return; } } } bool check(int l) { lim = l; num = 0; // printf("\nLen :%lld\n", l); dfs(1, 0); // printf("Num : %lld\n", num); return num >= m; } signed main() { int l = 1, r = 0, ans = 0; n = read(), m = read(); for (int i = 1; i < n; i++) { int u = read(), v = read(), w = read(); ver[u].push_back(make_pair(v, w)); ver[v].push_back(make_pair(u, w)); r += w; } while (l <= r) { int mid = (l + r) >> 1; if (check(mid)) { ans = mid; l = mid + 1; } else r = mid - 1; } printf("%lld ", ans); return 0; }