题目描述
\(H\) 国有 \(n\) 个城市,这 \(n\) 个城市用 \(n-1\) 条双向道路相互连通构成一棵树,\(1\) 号城市是首都,也是树中的根节点。
\(H\) 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 \(H\) 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
输入输出格式
输入格式:
第一行一个整数\(n\),表示城市个数。
接下来的 \(n−1\) 行,每行 \(3\) 个整数,\(u,v,w\),每两个整数之间用一个空格隔开,表示从城市 \(u\) 到城市 \(v\) 有一条长为 \(w\) 的道路。数据保证输入的是一棵树,且根节点编号为 \(1\)。
接下来一行一个整数 \(m\),表示军队个数。
接下来一行 \(m\) 个整数,每两个整数之间用一个空格隔开,分别表示这 \(m\) 个军队所驻扎的城市的编号。
输出格式:
一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出 \(-1\) 。
输入输出样例
输入样例#1:
4
1 2 1
1 3 2
3 4 3
2
2 2
输出样例#1:
3
分析
方法:贪心+倍增+二分
这道题细节恶心,改了我一晚上。。。。。。
考虑二分答案,然后在\(check\)函数里每次贪心的不断将军队往上跳,这样能使覆盖到的范围越大。同时记录下能到达根节点的军队,找到未被覆盖的子树,这时便可以用这些军队去控制这些节点。但这样做肯定会超时,所以我们用倍增来优化一下。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 50003
using namespace std;
typedef long long ll;
template <typename T> void read(T &x) {
T f = 1; x = 0; char ch;
for (ch = getchar(); !isdigit(ch); ch=getchar()) if(ch == '-') f = -1;
for ( ; isdigit(ch); ch=getchar()) x = x * 10 + (ch ^ 48);
x *= f;
}
struct edge {
int to, nxt;
ll val;
}e[maxn<<1];
struct ctrl {
ll rest;
int id;
}ar[maxn], br[maxn];
int n, m;
int ca, cb;
int cnt, head[maxn<<1];
int fa[maxn][20];
ll dis[maxn][20], mn[maxn];
int army[maxn], mark[maxn];
bool vis[maxn], covered[maxn];
bool cmp(ctrl a, ctrl b) {
return a.rest > b.rest;
}
void add_edge(int u, int v, ll w) {
e[++cnt].to = v, e[cnt].val = w, e[cnt].nxt = head[u], head[u] = cnt;
}
void dfs(int u, int _fa, ll dist) {
fa[u][0] = _fa, dis[u][0] = dist;
for (int i = 1; i <= 17; ++i)
fa[u][i] = fa[fa[u][i-1]][i-1], dis[u][i] = dis[fa[u][i-1]][i-1] + dis[u][i-1];
for (int i = head[u]; i >= 0; i = e[i].nxt){
int v = e[i].to;
if (v == _fa) continue;
dfs(v, u, e[i].val);
}
}
bool jud(int u, int _fa) {
if (vis[u]) return 1;
bool f1 = 1, f2 = 0;
for (int i = head[u]; i >= 0; i = e[i].nxt){
int v = e[i].to;
if (v == _fa) continue;
f2 = 1;
if (!jud(v, u)) {
f1 = 0;
if (u == 1) br[++cb].rest = e[i].val, br[cb].id = v;
else return 0;
}
}
if (!f2) return 0;
return f1;
}
bool check(int lim) {
ca = cb = 0;
for (int i = 1; i <= n; ++i) vis[i] = 0, mark[i] = 0;
for (int i = 1; i <= m; ++i) covered[i] = 0;
ll dist;
int u;
for (int i = 1; i <= m; ++i){
dist = 0; u = army[i];
for (int j = 17; j >= 0; --j)
if (fa[u][j] > 1 && dist + dis[u][j] <= lim)
dist += dis[u][j], u = fa[u][j];
if (fa[u][0] == 1 && dis[u][0] + dist <= lim) {
ar[++ca].rest = lim - dis[u][0] - dist, ar[ca].id = i;
if (!mark[u] || ar[ca].rest < mn[u]) mark[u] = i, mn[u] = ar[ca].rest;
}else vis[u] = 1;
}
if (jud(1, 0)) return 1;
sort(ar + 1, ar + 1 + ca, cmp);
sort(br + 1, br + 1 + cb, cmp);
u = 1, covered[0] = 1;
for (int i = 1; i <= cb; ++i){
if (!covered[mark[br[i].id]]) {covered[mark[br[i].id]] = 1; continue;}
while ((u <= ca) && (covered[ar[u].id] || ar[u].rest < br[i].rest)) ++u;
if (u > ca) return 0;
covered[ar[u].id]=1;
}
return 1;
}
int main() {
// freopen("blockade.in", "r", stdin);
// freopen("blockade.out", "w", stdout);
int ans = -1;
int u, v, w;
read(n);
for (int i = 1; i <= n; ++i) head[i] = -1;
for (int i = 1; i < n; ++i){
read(u), read(v), read(w);
add_edge(u, v, w); add_edge(v, u, w);
}
dfs(1, 0, 0);
read(m);
for (int i = 1; i <= m; ++i) read(army[i]);
int l = 0, r = 500000;
while (l <= r){
int mid = l + r >> 1;
if ( check(mid) ) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d", ans);
return 0;
}