[P3523]DYN-Dynamite(二分+樹形dp)
【原題】
題目描述
The Byteotian Cave is composed of nnn chambers and n−1n-1n−1 corridors that connect them. For every pair of chambers there is unique way to move from one of them to another without leaving the cave.
Dynamite charges are set up in certain chambers.
A fuse is laid along every corridor.
In every chamber the fuses from the adjacent corridors meet at one point, and are further connected to the dynamite charge if there is one in the chamber. It takes exactly one unit of time for the fuse between two neighbouring chambers to burn, and the dynamite charge explodes in the instant that fire reaches the chamber it is inside.
We would like to light the fuses in some mmm chambers (at the joints of fuses) in such a way that all the dynamite charges explode in the shortest time possible since the fuses are lit. Write a program that will determine the minimum such time possible.
給一棵樹,書上有一些關鍵節點,要求你選m個點,使得關鍵節點到這些點中距離的最小值的最大值最小,求這個值
輸入格式
The first line of the standard input holds two integers nnn and mmm(1≤m≤n≤300 0001\le m\le n\le 300\ 0001≤m≤n≤300 000), separated by a single space, that denote, respectively, the number of chambers in the cave and the number of chambers in which fire can be set to the fuses.
The chambers are numbered from 1 to nnn.
The next line contains nnn integers d1,d2,⋯ ,dnd_1,d_2,\cdots,d_nd1,d2,⋯,dn (di∈{0,1}d_i\in {0,1}di∈{0,1}), separated by single spaces.
If di=1d_i=1di=1, then there is dynamite in the iii-th chamber, and if di=0d_i=0di=0, there is none.The following n−1n-1n−1 lines specify the corridors of the cave. Each of them holds two integers a,ba,ba,b(1≤a<b≤n1\le a<b\le n1≤a<b≤n), separated by a single space, denoting that there is a corridor connecting the chambers aaa and bbb. Every corridor appears exactly once in the description.
You may assume that in tests worth 10% of the points it holds additionally that n≤10n\le 10n≤10 , while in tests worth 40% of the points it holds that n≤1 000n\le 1\ 000n≤1 000
輸出格式
The first and only line of the standard output should hold a single integer, equal to the minimum time it takes from lighting the fuses to the explosion of all the charges.
輸入輸出樣例
輸入 #1
7 2
1 0 1 1 0 1 1
1 3
2 3
3 4
4 5
5 6
5 7
輸出 #1
1
說明/提示
給一棵樹,書上有一些關鍵節點,要求你選m個點,使得關鍵節點到這些點中距離的最小值的最大值最小,求這個值
【思路】
二分答案,考慮是否可行。
選擇至多\(m\)個點, 將關鍵節點(\(a[i] == 1\))覆蓋, 使得所有關鍵節點到最近的選擇點的最大值最小(關鍵節點可以被選中)。
k值為二分的mid值, num值為選擇的點個數。
\(f1[u]\)表示u的子樹中最遠的未覆蓋, \(f1[u] = -inf\) 表示該點已經被覆蓋。
\(f2[u]\) 表示u的子樹中最近的選擇點。
\(\begin{cases} f1[u] = max(f1[u], f1[v] + 1);\\f2[u] = min(f2[u], f2[v] + 1); \end{cases}%\)
考慮當前點是否已經被覆蓋 / 需要被選擇,考慮三種情況:
- 當前節點是關鍵節點,最近的已選擇 > k ,無法被覆蓋, 交由父節點處理(如果著急將這個點選擇,num值不是最小, 出於貪心,先不選擇)。 \(f1[u] = max(0, f1[u])\)
- 子樹最遠的未覆蓋 + 子樹最近的已選擇 < k,整個子樹可以被覆蓋。\((f1[u] + f2[u] <= k) f1[u] = -inf\)
- 子樹最遠的未覆蓋 == k, 必須將該點選擇,否則必不符合所有點到最近關鍵點的最大值 <= k。$ f1[u] = -inf, f2[u] = 0$
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <list>
#include <map>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define LL long long
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f
#define PI 3.1415926535898
#define F first
#define S second
#define endl '\n'
#define lson rt << 1
#define rson rt << 1 | 1
#define f(x, y, z) for (int x = (y), __ = (z); x < __; ++x)
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
const int maxn = 3e6 + 7;
int n, m;
int a[maxn], f1[maxn], f2[maxn], num;
struct pp
{
int v, w, next;
}edge[2 * maxn];
int head[maxn], cnt;
void add(int t1, int t2, int t3)
{
edge[++cnt].v = t2;
edge[cnt].w = t3;
edge[cnt].next = head[t1];
head[t1] = cnt;
}
void dfs(int u, int f, int k)
{
f1[u] = -inf, f2[u] = inf;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].v;
if (v == f) continue;
dfs(v, u, k);
f1[u] = max(f1[u], f1[v] + 1);
f2[u] = min(f2[u], f2[v] + 1);
}
if (a[u] && f2[u] > k) f1[u] = max(0, f1[u]);//無法被子樹的點覆蓋 交給父節點處理
if (f1[u] + f2[u] <= k) f1[u] = -inf;//子樹可被覆蓋
if (f1[u] == k)//必須作為覆蓋點
{
f1[u] = -inf, f2[u] = 0;
num++;
}
//不能用else if
}
int check(int v)
{
num = 0;
dfs(1, -1, v);
if (f1[1] >= 0) num++; //考慮根節點無法被覆蓋的情況
return num <= m;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
_rep(i, 1, n) cin >> a[i];
int ta, tb;
_rep(i, 1, n - 1)
{
cin >> ta >> tb;
add(ta, tb, 1);
add(tb, ta, 1);
}
int l = 0, r = n;
while (l + 1 < r)
{
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
if (check(l)) r = l;//不加這句會有一個點過不了
cout << r << endl;
}