[POI2011][樹上貪心] DYN-Dynamite
阿新 • • 發佈:2020-08-24
題面
一道比較巧妙的樹上貪心題,做法同Luogu P3942 將軍令
[HNOI2003]消防局的設立類似。
首先分析題面不難想到二分答案,然後我們維護兩個量:
1. 當前子樹下最遠的需要覆蓋(有炸彈)的點,記作 \(disCo_x\)
2. 當前子樹外最近的被選擇引燃的點,記作 \(disSel_x\)。
將所有點分為三種情況考慮:
1. 自身就是等待覆蓋的關鍵節點(炸彈),即 \(explo_x = True\),\(disSel_x \gt val\),此時 \(disCo_x\) 一定為 \(0\) (子樹無炸彈)或原先值,
2. 子樹中所有的炸彈被子樹外的點覆蓋,即 \(disSel_x + disCo_x \le val\)
不存在(\(disCo_x = -inf\))。
3. 當前點是需要選擇的節點,即 \(disCo_x = val\),此時 \(cnt = cnt + 1\)。
\(val\) 為二分出的最小距離最大值,\(cnt\) 為選擇的節點總數。
然後跑個 \(DFS\),這題就切掉了。
程式碼:
# include <iostream> # include <cstdio> # define MAXN 300005 # define INF 1145141919.810 struct edge{ int v, next; }e[MAXN<<1]; int hd[MAXN], cntE; bool explo[MAXN]; // is there a bomb or not int disCo[MAXN], disSel[MAXN]; // dis to futhest uncovered key node, dis to closet selected key node int cntSel, n, m; void AddE(int u, int v); void DFS(int now, int fa, int val); bool Chk(int val); int main(){ scanf("%d%d", &n, &m); for(int i = 1, x; i <= n; i++){ scanf("%d", &x); explo[i] = x; } for(int i = 1, u, v; i <= n-1; i++){ scanf("%d%d", &u, &v); AddE(u, v); AddE(v, u); } int l = 0, r = n; while(l < r){ int mid = (l + r) >> 1; if(Chk(mid)){ r = mid; } else{ l = mid+1; } } printf("%d", l); return 0; } bool Chk(int val){ cntSel = 0; DFS(1, 0, val); if(disCo[1] >= 0){ cntSel++; } return cntSel <= m; // 選擇的節點不能超過 m 個 } void DFS(int now, int fa, int val){ // val 為二分的最大值 disCo[now] = -INF, disSel[now] = INF; for(int i = hd[now]; i; i = e[i].next){ if(e[i].v == fa){ continue; } DFS(e[i].v, now, val); disCo[now] = std::max(disCo[now], disCo[e[i].v] + 1); disSel[now] = std::min(disSel[now], disSel[e[i].v] + 1); } if(explo[now] && disSel[now] > val){ // 自身是需要被覆蓋的節點 disCo[now] = std::max(disCo[now], 0); } if(disCo[now] + disSel[now] <= val){ // 內部未被覆蓋的點均被子樹外的點覆蓋 disCo[now] = -INF; } if(disCo[now] == val){ // 當前節點恰好是需要選擇的節點 disCo[now] = -INF; disSel[now] = 0; cntSel++; } } void AddE(int u, int v){ e[++cntE] = (edge){v, hd[u]}; hd[u] = cntE; }