题目描述
H H H 国有 n n n个城市,这 n n n 个城市用 n − 1 n-1 n−1 条双向道路相互连通构成一棵树, 1 1 1 号城市是首都,也是树中的根节点。
H H H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 H H H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
输入格式
第一行一个整数 n n n ,表示城市个数。
接下来的 n − 1 n-1 n−1 行,每行 3 3 3 个整数, u , v , w u,v,w u,v,w,每两个整数之间用一个空格隔开,表示从城市 u u u 到城市 v v v 有一条长为 w w w 的道路。数据保证输入的是一棵树,且根节点编号为 1 1 1 。
接下来一行一个整数 m m m,表示军队个数。
接下来一行 m m m 个整数,每两个整数之间用一个空格隔开,分别表示这 m m m 个军队所驻扎的城市的编号。
输出格式
一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出 − 1 -1 −1 。
输入输出样例
输入
4
1 2 1
1 3 2
3 4 3
2
2 2
输出
3
说明/提示
【输入输出样例说明】
第一支军队在 2 2 2 号点设立检查点,第二支军队从 2 2 2 号点移动到 3 3 3 号点设立检查点,所需时间为 3 3 3 个小时。
【数据范围】
保证军队不会驻扎在首都。
对于 20 % 20\% 20% 的数据, 2 ≤ n ≤ 10 2≤ n≤ 10 2≤n≤10 ;
对于 40 % 40\% 40% 的数据, 2 ≤ n ≤ 50 , 0 < w < 1 0 5 2 ≤n≤50,0<w <10^5 2≤n≤50,0<w<105 ;
对于 60 % 60\% 60% 的数据, 2 ≤ n ≤ 1000 , 0 < w < 1 0 6 2 ≤ n≤1000,0<w <10^6 2≤n≤1000,0<w<106 ;
对于 80 % 80\% 80% 的数据, 2 ≤ n ≤ 10 , 000 2 ≤ n≤10,000 2≤n≤10,000 ;
对于 100 % 100\% 100% 的数据, 2 ≤ m ≤ n ≤ 50 , 000 , 0 < w < 1 0 9 2≤m≤n≤50,000,0<w <10^9 2≤m≤n≤50,000,0<w<109 。
NOIP 2012 提高组 第二天 第三题
题目大意
给定一棵树和树上的一些障碍,用这些障碍来切断根节点到所有叶子节点(注意是单指叶子节点,而不是除了根节点外的所有节点)的路径,可以移动障碍,求移动时间最久的障碍移动的最小时间是多少。
解题思路
题目要求的是移动时间最久的障碍移动的最小时间是多少,所以自然而然就想到了 二 分 答 案 \color{red}二分答案 二分答案 ,用来二分最小时间 。
明确了方向之后,接下来就需要确定二分答案中最重要的 判定合法 了 。
那如何判定合法呢?
首先可以想到假如二分答案后得出的限定时间为 x x x , 那么我们可以将所有障碍在这个限定时间内提上调到这个障碍能到达的最高的高度,这个步骤可以用 树 上 倍 增 \color{red}树上倍增 树上倍增 优化 。
因为你这个障碍越高,那他能堵住的路径自然就多了嘛,事实上就是 贪 心 \color{red}贪心 贪心 。
然后,可以得出上调的两种情况:
- 若这个障碍无法在限定时间内到达根节点,那么他最高的位置一定是最优的,让他待在那里就可以了,并用
v
i
s
vis
vis 数组记录这个障碍物已经
找到他m了找到家了,后面无须对其进行操作。 - 若这个障碍可以在限定时间内到达根节点,则令其 暂时停留(后面有用) 在根节点的子节点上(这是唯一的,位于这个障碍原先的位置到根节点的路径上),然后用数组 b b b (是个结构体)记录这个障碍到达根节点后的剩余时间和他目前所在节点(根节点的其中一个子节点)的编号 。
接下来用 d f s dfs dfs 对整棵树遍历一次,用 数组 n e e d need need 记录有哪些节点到根节点的路径还没被堵住(如果一个节点到根节点的路径还没被堵住就记为 1 1 1 ,否则记为 0 0 0 )。
先解决一些问题,
Q:为什么要记录可以在限定时间内到达根节点的障碍?
A:因为这是一棵树,如果你要从根节点的一个子节点去支援根节点的另一个子节点的话(如果无法在限定时间内到达根节点的障碍肯定无法去支援了,会不会被骂啊? ),就必须跨过根节点,所以就得先记录一下,后面再处理。
好了,前面说了这么多,终于可以处理闲置的障碍了,
先将
b
b
b 数组按照剩余时间升序排序,然后遍历
b
b
b 数组,如果这个闲置的障碍物到根节点的路径还没被堵住的话,就用
c
1
c1
c1 数组将这个障碍物的剩余时间记录下来,但这里就有个问题,有些障碍跳到根节点回不来怎么办?那么此时我们就可以大胆猜想:留住一个回不去的深度为
2
2
2 的结点,其余点贪心地进行分配,怎么又是贪心? 。
为什么这样贪心是对的,给出一个简略证明:
如果一个点
x
x
x 跳到根节点但无法回去,此时另外一个点
y
y
y 来管辖
x
x
x 之前的所在的子树,
x
x
x 现在可能去管辖另外一个以
z
z
z 为根节点的子树,满足
d
i
s
(
z
,
r
t
)
<
d
i
s
(
x
,
r
t
)
dis(z,rt)<dis(x,rt)
dis(z,rt)<dis(x,rt) 。我们很容易知道,
y
y
y 肯定也能去管辖
z
z
z 所在子树的。因为
y
y
y 能够移动的距离更长,所以
y
y
y 有更多的可能性 前途更加光明),因此不如让剩余时间多的节点去驻扎,而自己留在当前节点即可 。
由此可以改进一下情节,先将 b b b 数组按照剩余时间升序排序,然后遍历 b b b 数组,如果这个闲置的障碍物到根节点的路径还没被堵住的话,就先留住一个跳到根节点回不来的深度为 2 2 2 的障碍物,否则,用 c 1 c1 c1 数组将这个障碍物的剩余时间记录下来,
好了,该回归主线了,倒数第二章,遍历根节点的每一个子节点,如果还没被堵住,用数组 c 2 c2 c2 记录这个子节点到根节点的时间即可 。
注意, c 1 c1 c1 记录的是还闲置的障碍物的剩余时间, c 2 c2 c2 记录的是需要堵住的节点到根节点的时间 。
最后,只需要用 c 1 c1 c1 来补 c 2 c2 c2 就行了:分别将数组 c 1 c1 c1 和 c 2 c2 c2 按照升序排个序,再用双指针配对即可 。
时间复杂度 : O ( n log 2 n w ) O(n \log^2 nw) O(nlog2nw)
特:做法 2
其实我们与其对于每一个点都通过倍增向上找到对应位置,还不如直接从上到下 d f s dfs dfs 一遍,判断:如果当前点子树内初始位置最浅的军队与当前点距离不超过 m i d mid mid ,或者所有子树都被封锁,那么当前点也被封锁。
这样以后再二分,时间复杂度降至 O ( n log n w ) O(n\log nw) O(nlognw) 。
A C C O D E AC \ CODE AC CODE
最后放上 A C AC AC 代码,大家慢慢食用——
#include<bits/stdc++.h>
#define int long long
#define _ (int)5e4 + 5
#define t (int)log2(_) + 1
using namespace std;
int n, m, l, r, ans, flag;
array<int, _> a;
int tot;
array<int, _> head;
array<int, _ << 1> to, nxt, w;
array<int, _> dep;
array<int, 21> fa[_], dis[_];
int ret, cc1, cc2;
array<int, _>vis, need, c1, c2;
array<pair<int, int>, _>b;
void add(int u, int v, int val)
{
to[++tot] = v;
w[tot] = val;
nxt[tot] = head[u];
head[u] = tot;
}
void bfs()
{
queue<int> q;
q.push(1);
dep[1] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dep[v])continue;
dep[v] = dep[u] + 1;
fa[v][0] = u;
dis[v][0] = w[i];
for (int j = 1; j <= t; ++j)
{
fa[v][j] = fa[fa[v][j - 1]][j - 1];
dis[v][j] = dis[v][j - 1] + dis[fa[v][j - 1]][j - 1];
}
q.push(v);
}
}
}
bool dfs(int u)
{
bool flag = 0;
if (vis[u])return 1;
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (dep[v] < dep[u])continue;
flag = 1;
if (!dfs(v))return 0;
}
if (!flag)return 0;
return 1;
}
bool check(int limit)
{
ret = 0;
cc1 = cc2 = 0;
vis.fill(0);
need.fill(0);
b.fill(make_pair(0, 0));
c1.fill(0);
c2.fill(0);
for (int i = 1; i <= m; ++i)
{
int x = a[i], cnt = 0;
for (int j = t; j >= 0; j--)
{
if (fa[x][j] > 1 && cnt + dis[x][j] <= limit)
{
cnt += dis[x][j];
x = fa[x][j];
}
}
if (fa[x][0] == 1 && cnt + dis[x][0] <= limit)
b[++ret] = make_pair(limit - cnt - dis[x][0], x);
else
vis[x] = 1;
}
for (int i = head[1]; i; i = nxt[i])
if (!dfs(to[i]))need[to[i]] = 1;
sort(b.begin() + 1, b.begin() + ret + 1);
for (int i = 1; i <= ret; ++i)
{
if (need[b[i].second] && b[i].first < dis[b[i].second][0])
need[b[i].second] = 0;
else
c1[++cc1] = b[i].first;
}
for (int i = head[1]; i; i = nxt[i])
if (need[to[i]])c2[++cc2] = dis[to[i]][0];
if (cc1 < cc2) return 0;
sort(c1.begin() + 1, c1.begin() + cc1 + 1);
sort(c2.begin() + 1, c2.begin() + cc2 + 1);
int i = 0, j = 0;
while (i < cc1 && j < cc2)
{
if (c1[i + 1] >= c2[j + 1])
i++, j++;
else i++;
}
if (j == cc2)return 1;
return 0;
}
signed main()
{
scanf("%lld", &n);
for (int i = 1; i < n; ++i)
{
int u, v, val;
scanf("%lld%lld%lld", &u, &v, &val);
add(u, v, val);
add(v, u, val);
r += val;
}
scanf("%lld", &m);
for (int i = 1; i <= m; i++) scanf("%lld", &a[i]);
bfs();
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
r = mid - 1;
ans = mid;
flag = 1;
}
else
l = mid + 1;
}
if (!flag)
printf("-1\n");
else
printf("%lld\n", ans);
return 0;
}