1218 疫情控制
2012年NOIP全国联赛提高组
时间限制: 2 s
空间限制: 128000 KB
题目等级 : 钻石 Diamond
题目描述 Description
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,
也是树中的根节点。
H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境
城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境
城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,
首都是不能建立检查点的。
现在,在 H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军
队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在
一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等
于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
输入描述 Input Description
第一行一个整数 n,表示城市个数。
接下来的 n-1 行,每行 3 个整数,u、v、w,每两个整数之间用一个空格隔开,表示从
城市 u 到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。
接下来一行一个整数 m,表示军队个数。
接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎
的城市的编号。
输出描述 Output Description
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
样例输入 Sample Input
4
1 2 1
1 3 2
3 4 3
2
2 2
样例输出 Sample Output
3
数据范围及提示 Data Size & Hint
【输入输出样例说明】
第一支军队在 2 号点设立检查点,第二支军队从 2 号点移动到 3 号点设立检查点,所需
时间为 3 个小时。
【数据范围】
保证军队不会驻扎在首都。
对于 20%的数据,2≤ n≤ 10;
对于 40%的数据,2 ≤n≤50,0<w <10^5;
对于 60%的数据,2 ≤ n≤1000,0<w <10^6;
对于 80%的数据,2 ≤ n≤10,000;
对于 100%的数据,2≤m≤n≤50,000,0<w <10^9。
分析:有木有发现这道题和引水入城真的超级像?判定性问题+最小值=二分,既然求时间,那么我们就二分时间呗.然后问题就成了怎么判断在二分的时间内能否控制住呢?因为题目给出的是一棵树,可以知道越往上可以控制的结点就越多,那么我们把所有能往上移的军队往上移,怎么移呢?当然是倍增啦,如果到了根节点(如果能设检查点就好了),那么记录下根节点到这个军队的初始地点的路径上的根节点的子节点.为什么呢?因为肯定自己覆盖自己所在的那条路径的最好啦,如果让别的军队来覆盖,代价会变大.把所有不能到达根节点的军队到达的最高结点打上标记,然后dfs一遍,计算出第二层有多少没有打上标记,注意,如果一个结点的子节点都打上了标记,那么这个结点也要打上标记,这样需要判断的结点都上移了,只需要判断在根节点的军队能否覆盖剩下的第二层节点,怎么判断呢?很容易想到剩余时间最小的去覆盖长度最小的,然后模拟模拟就过了.
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100010;
int head[maxn * 2], nextt[maxn * 2], to[maxn * 2], chang[maxn],tot,a[maxn];
long long f[maxn][18], g[maxn][18];
bool vis[maxn];
long long lencity, lenarmy,from1,juli;
int n, m,u,v,w;
struct node
{
int lefttime, from;
}s[maxn];
struct node2
{
int juli, from;
}l[maxn];
bool cmp1(node a, node b)
{
return a.lefttime < b.lefttime;
}
bool cmp2(node2 a, node2 b)
{
return a.juli < b.juli;
}
void dfs2(int x)
{
bool up = true, down = true;
for (int i = head[x]; i; i = nextt[i])
if (to[i] != f[x][0])
{
dfs2(to[i]);
down = false;
if (vis[to[i]] == false)
up = false;
}
if (up && x != 1 && down == false)
vis[x] = true;
}
void add(int x, int y, int w)
{
to[++tot] = y;
nextt[tot] = head[x];
head[x] = tot;
chang[tot] = w;
}
void dfs(int u, int from)
{
for (int i = head[u];i;i = nextt[i])
{
if (to[i] != from)
{
f[to[i]][0] = u;
g[to[i]][0] = chang[i];
dfs(to[i], u);
}
}
}
bool check(int x)
{
memset(vis, false, sizeof(vis));
lencity = lenarmy = 0;
for (int i = 1; i <= m; i++)
{
from1 = a[i];
juli = 0;
for (int j = 17; j >= 0; j--) //将军队往上移到最高
{
if (juli + g[from1][j] <= x && f[from1][j])
{
juli += g[from1][j];
from1 = f[from1][j];
}
}
if (from1 != 1)
vis[from1] = true;
else
{
s[++lencity].lefttime = x - juli;
from1 = a[i];
for (int j = 17; j >= 0; j--)
if (f[from1][j] > 1)
from1 = f[from1][j];
s[lencity].from = from1;
}
}
dfs2(1); //把能控制的点都给标记
sort(s + 1, s + 1 + lencity, cmp1);
for (int i = head[1]; i; i = nextt[i])
{
if (!vis[to[i]])
{
l[++lenarmy].juli = chang[i];
l[lenarmy].from = to[i];
}
}
sort(l + 1, l + 1 + lenarmy, cmp2);
if (lencity < lenarmy)
return false;
l[lenarmy + 1].juli = 0x7ffffffff; //防止访问无效空间
int k = 1;
for (int i = 1; i <= lencity; i++)
{
if (!vis[s[i].from])
vis[s[i].from] = true;
else if (s[i].lefttime >= l[k].juli)
{
vis[l[k].from] = true;
k++;
}
while (vis[l[k].from]) //防止出现二次匹配的情况
k++;
}
if (k > lenarmy)
return true;
else
return false;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n - 1; i++)
{
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
add(v, u, w);
}
dfs(1, 0);
for (int j = 1; j <= 17; j++) //倍增大法好
for (int i = 1; i <= n; i++)
{
f[i][j] = f[f[i][j - 1]][j - 1];
g[i][j] = g[f[i][j - 1]][j - 1] + g[i][j - 1];
}
scanf("%d", &m);
for (int i = 1; i <= m; i++)
scanf("%d", &a[i]);
int l = 0, r = 1000000000;
while (l < r)
{
int mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid + 1;
}
printf("%d\n", l);
return 0;
}
技巧:判定性问题+最值可以用二分来求.