???+note "[2014 全國賽 p4](https://cs.cysh.cy.edu.tw/competition_problem_set/%E5%85%A8%E5%9C%8B103.pdf#page=9)" 給一顆 $n$ 個點,邊有權重的樹,選一個連通塊,連通塊內所有的邊權總和不能超過 $L$,問所有「沒被選到的點」到「有被選到的點」的最短距離最大的,最少可以是多少
![Image title](./images/14.png){ width="300" }
$n\le 10^6,L\le 2\times 10^8$ ??? note "思路" 顯然我們沒辦法去直接維護要選那些邊作為 optimal 的連通塊。那不如反著做 ? 一開始我們把全部的邊都選起來,慢慢從 leaf 開始移除,每次移除時要選擇對答案影響最小的,直到當前未移除的連通塊的邊權總和 $\le L$。過程利用 priority queue 維護 ???+note "[2015 全國賽 p5](https://tioj.ck.tp.edu.tw/problems/1915)" 給一張 $n$ 點 $m$ 邊無向圖,你要將點由 $1\sim n$ 編號,問 $ans$ 最小可以是多少。計算方法如下 : $$cost_u = u \space 周圍的點 \space v \space 有幾個編號比他大 $$ $$ans = \max \limits_{u=1\sim n}\{ cost_u \} $$ 也就是最小化最大值 $n\le 5\times 10^5,m\le 8\times 10^5$ ??? note "思路" > 法 1 : 二分搜 考慮二分搜答案,檢查答案是否合法 ??? note "check function 實作" ```cpp linenums="1" // 檢查只能用 degree <= x 的是否可以達成 bool topo(int x) { queue q; vector vis(n + 1); for (int i = 1; i <= n; i++) { if (deg[i] <= x) q.push(i); } while (q.size()) { int u = q.top(); q.pop(); vis[u] = true; for (auto v : G[u]) { if (vis[v]) continue; deg[v]--; if (deg[v] <= x) q.push(v); } } for (int i = 1; i <= n; i++) { if (vis[i] == false) return false; } return true; } ``` 複雜度 : $O((n+m)\log n)$ --- > 法 2 : greedy 想法 依序枚舉 1 ~ n,放在 `deg[i]` 最小的,用 pq 維護當前 deg 最小的 將某個點 u 周圍的 `deg[v]--` 的話要怎麼做 ? 想想看 dijkstra 是怎麼做到這件事的 就像 dijkstra 那樣直接 push 一個新的進去 pq 即可 pq 再拿某個點出來時記得檢查是否過期 複雜度 : $O((n+m)\log n)$ --- > 法 3 : 優化法 2 開 $n$ 個 vector,`v[i]` 放所有 degree = i 的點 ??? note "Data structure" ```cpp linenums="1" struct DS { vector> nodes; int n, threshold = 0; DS(int n) : n(n) { nodes.resize(n + 1); } void push(int u) { nodes[max(threshold, deg[u])].pb(u); } int get_min(int u) { while (threshold <= n && nodes[threshold].empty()) { threshold++; } if (threshold <= n && nodes[threshold].size()) { int ret = nodes[threshold].back(); nodes[threshold].pop_back(); return ret; } assert(0); } } pq; ``` 複雜度 : $O(n+m)$ ??? note "code" ```cpp linenums="1" #include #define int long long #define pii pair #define pb push_back #define mk make_pair #define F first #define S second #define ALL(x) x.begin(), x.end() using namespace std; const int INF = 2e18; const int maxn = 5e5 + 5; const int M = 1e9 + 7; int n, m; vector G[maxn]; int deg[maxn], vis[maxn], ans[maxn]; struct DS { vector> nodes; int n, threshold = 0; DS(int n) : n(n) { nodes.resize(n + 1); } void push(int u) { nodes[max(threshold, deg[u])].pb(u); } int get_min() { while (threshold <= n && nodes[threshold].empty()) { threshold++; } if (threshold <= n && nodes[threshold].size()) { int ret = nodes[threshold].back(); nodes[threshold].pop_back(); return ret; } assert(0); } }; void solve() { DS ds(n); for (int i = 0; i < n; i++) { ds.push(i); } for (int i = 0; i < n; i++) { int u = ds.get_min(); while (vis[u]) { u = ds.get_min(); } vis[u] = true; for (auto v : G[u]) { deg[v]--; ds.push(v); } } cout << ds.threshold << '\n'; } void init() { cin >> n >> m; for (int i = 0; i < n; i++) { G[i].clear(); deg[i] = 0; vis[i] = 0; ans[i] = 0; } for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; ans[min(u, v)]++; G[u].pb(v); G[v].pb(u); deg[u]++; deg[v]++; } cout << *max_element(ans, ans + n) << ' '; } signed main() { ios::sync_with_stdio(0); cin.tie(0); int t = 1; cin >> t; while (t--) { init(); solve(); } } ``` ???+note "[2017 全國賽 p5](https://tioj.ck.tp.edu.tw/problems/2038)" 給你一張 $n$ 點 $m$ 邊無向圖,選一些點 ( 我們把這個點集合叫做 $S$ ),$S$ 內的點必須連通 問 $F(S)$ 最大多少 $$F(S) = \lvert S \rvert \min\limits_{1\leq i \leq \lvert S \rvert} \{ D_i \}$$ 其中 $D_i$ 表示 $i$ 的鄰居裡面,有多少人在 $S$ 內 $n\le 5000$ ??? note "思路" 我們考慮從枚舉 $D_i$ 下手 - $D_i=1$ 這時其實就是整張圖 - $D_i=2$ 因為 degree 是 $1$ 的點不能選,所以刪除 $\deg \le 1$ 的點,並將周圍的點的 degree -1 - $D_i=3$ 刪除 $\deg \le 2$ 的點,並將周圍的點的 degree -1 - $D_i=4$ 刪除 $\deg \le 3$ 的點,並將周圍的點的 degree -1 - ... 可以觀察到當 $D_i=k$ 的時候需要將 $\deg \le k-1$ 的點刪掉 此時 $F(S)=$ 剩下的點的數量 $\times (k-1)$ --- 實作方面沒辦法真的去刪邊,考慮時光倒流技巧,用加邊來看 只是你並不能知道要依序加哪些點進去 所以我們先預處理好 $D_i=k+1 \to D_i=k$ 的時候需要加入的點即可 預處理就像 topo 排序那樣每輪找 $\deg = k$ 的點,將他們存在 vector `v[k]` 裡面 ??? note "code" ```cpp linenums="1" #include #define int long long #define pii pair #define pb push_back #define mk make_pair #define F first #define S second #define ALL(x) x.begin(), x.end() using namespace std; const int INF = 2e18; const int maxn = 5e3 + 5; const int M = 1e9 + 7; int n, m; vector G[maxn]; vector V[maxn]; int in[maxn], vis[maxn], par[maxn], sz[maxn]; void dsu_init() { for (int i = 1; i <= n; i++) { par[i] = i; sz[i] = 1; } } int find(int x) { if (par[x] == x) return x; else return par[x] = find(par[x]); } void merge(int a, int b) { int x = find(a), y = find(b); if (x == y) return; par[x] = y; sz[y] += sz[x]; sz[x] = 0; } void init() { cin >> n >> m; int u, v; for (int i = 0; i < m; i++) { cin >> u >> v; G[u].pb(v); G[v].pb(u); in[u]++, in[v]++; } } void topo() { for (int i = 1; i <= n; i++) { // 刪除 deg[u] <= i 的點 queue q; for (int j = 1; j <= n; j++) { if (!vis[j] && in[j] == i) { q.push(j); vis[j] = 1; } } while (q.size()) { int u = q.front(); q.pop(); V[i].pb(u); for (auto v : G[u]) { in[v]--; if (vis[v]) continue; if (in[v] <= i) { q.push(v); vis[v] = 1; } } } } } void solve() { topo(); dsu_init(); memset(vis, 0, sizeof vis); int ans = 0; for (int i = n; i >= 1; i--) { for (auto u : V[i]) { vis[u] = 1; for (auto v : G[u]) { if (!vis[v]) continue; merge(u, v); } } for (int u = 1; u <= n; u++) { if (par[u] == u && vis[u]) { ans = max(ans, sz[u] * i); } } } cout << ans << "\n"; } signed main() { // ios::sync_with_stdio(0); // cin.tie(0); int t = 1; // cin >> t; while (t--) { init(); solve(); } } ```