[NOIP2012]疫情控制(二分答案+倍增+贪心)

时间:2021-09-24 19:15:45

Description

H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。 H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。 现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。 请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

\(Hint:2≤m≤n≤50,000,0 < w <10^9\)

Solution

可以发现,军队可以同时移动,那么就是求最大值最小,考虑二分答案,

接下来可以发现,军队越往上,控制的点越多,那么显然,军队应该尽量往上爬,

在树上向上爬,那就倍增吧(_

所以用贪心的思路,所有军队尽量往根节点爬,然后再考虑爬到一些没有军队的分支,

那么如果去支援分支,显然只要到根节点的某个子节点就能控制这个分支,

将向上能到达根节点的点存起来,用B数组储存,并且记录它是从根节点的哪个子节点上来的,因为可能不去支援其他点,就要返回到上一个点

而到不了根节点的点,保持在最高点为最优,将这个最高点标记,表示这个点被控制了,此时只有部分点被标记为控制的,

此时进行一个操作,对于一个非根节点的点,如果其子节点全被控制,那它也是被控制的,就这样从根节点遍历整棵树,记录所有被控制的点,用C数组储存

然后再考虑让可以到达根节点的点去支援没有被控制的根节点的子节点

接下来将没有被控制的根节点的子节点也存起来,然后将BC,2个数组按到根节点的距离排序

注意是从小到大!因为B数组要先考虑要不要返回上一个点,

然后判断是否C数组所有点都可以被控制即可

_)终于打完了,真的麻烦,细节挺多的,主要错误如下:

  1. 向上跳的时候,应先更新距离再更新点的编号
  2. 预处理倍增时,应从\(2^1\)开始算,因为\(2^0\)已经算过了
  3. !!!倍增最大可以到50000,但我看成5000,只开到\(2^{14}\),导致大数据挂

看来我倍增还是不熟练

Code

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 50010
using namespace std; struct info {
int to, nex, w;
} e[N * 2];
int n, m, tot, head[N * 2], ans, arm[N], l, r;
int _log, f[N][20], dis[N][20]; struct node {
int d, fr;
} b[N], c[N];
bool flag[N]; bool cmp(node a, node b) {return a.d < b.d;}
inline int read() {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
} inline void add_edge(int u, int v, int w) {
e[++tot].to = v;
e[tot].nex = head[u];
head[u] = tot;
e[tot].w = w;
} void dfs(int u, int fa) {
for (int j = 1; j <= _log; ++j) {
f[u][j] = f[f[u][j - 1]][j - 1];
dis[u][j] = dis[u][j - 1] + dis[f[u][j - 1]][j - 1];
} for (int i = head[u]; i; i = e[i].nex) {
int v = e[i].to;
if (v == fa) continue;
f[v][0] = u;
dis[v][0] = e[i].w;
dfs(v, u);
}
} void color(int u, int fa) {
int p = 1, q = 0;
for (int i = head[u]; i; i = e[i].nex) {
int v = e[i].to;
if (v == fa) continue;
color(v, u);
p &= flag[v];
q = 1;
}
if (p && q && u != 1)
flag[u] = 1;
} bool check(int mid) {
memset(flag, 0, sizeof(flag));
int cnt = 0, top = 0;
for (int i = 1; i <= m; ++i) {
int u = arm[i], cos = 0;
for (int j = _log; j >= 0; --j)
if (f[u][j] && dis[u][j] + cos <= mid) {
cos += dis[u][j];
u = f[u][j];
}
if (u != 1) flag[u] = 1;
else {
b[++cnt].d = mid - cos;
u = arm[i];
for (int j = _log; j >= 0; j--)
if (f[u][j] > 1)
u = f[u][j];
b[cnt].fr = u;
}
}
color(1, 0);
for (int i = head[1]; i; i = e[i].nex) {
int v = e[i].to;
if (!flag[v]) {
c[++top].d = e[i].w;
c[top].fr = e[i].to;
}
}
sort(b + 1, b + cnt + 1, cmp);
sort(c + 1, c + top + 1, cmp);
int j = 1; c[top + 1].d = 1e9;
for (int i = 1; i <= cnt; ++i) {
if (!flag[b[i].fr]) flag[b[i].fr] = 1;
else if (b[i].d >= c[j].d) flag[c[j].fr] = 1;
while (flag[c[j].fr]) j++;
}
return j > top;
} inline void Init() {
n = read();
_log = log(n) / log(2);
for (int i = 1; i < n; ++i) {
int u = read(), v = read(), w = read();
r += w;
add_edge(u, v, w);
add_edge(v, u, w);
}
m = read();
for (int i = 1; i <= m; ++i)
arm[i] = read();
} int main() {
Init();
dfs(1, 0);
ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid))
ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}