[NOIP2012 提高组] 疫情控制 题解
[NOIP2012 提高组] 疫情控制
题意:
给定一棵树,边有边权,有一些结点上有军队(可能不止一支),军队可移动。求最短的时间,使得军队移动后,从根到每个叶子结点的路径上都有军队驻扎。军队可以同时移动。
思路:
咳咳咳我当时读错题了以为这题虚高,然后才意识到边境结点只有叶子结点。
首先一个很明显的贪心就是,军队能往根移动就一定会往根移动,因为深度浅的结点能控制的叶子结点一定不少于深度深的结点。
然后我就不会了,看了题解。咳咳咳,然后既然要求最短的时间,想到二分。这题的重点就是二分后的 check 的写法。
首先还是像刚才的贪心,让军队能往上走就往上走,走的过程用倍增加速。如果有的军队能够走到根节点的子节点,那么就记录下来剩下的时间还有多少;走不到的,就对终点打上标记,代表这个子树已经被封死了。注意根节点的子节点很特殊!,要特殊处理,所以就不打标记。这是第一遍 dfs。
然后是第二遍 dfs,这一遍是在处理子树是否被封死,分以下几种情况:
- 是叶子,但没有标记,则未被封死。
- 有标记,且不是根节点的子节点,被封死。
- 不是叶子,没有标记,但所有的儿子都被封死,被封死。
这样处理完后,我们就得到了根结点儿子的所有信息了。最后一步,就是分配军队。下面所说的上传军队,都是在把剩余时间大于到根的距离的军队上传。
对于一个根节点的子节点,也要分情况:
- 如果被封死,则所有的军队都可以上传;
- 如果没被封死,如果最小的军队的剩余时间不足以从该结点走上根节点再走下来,则留驻更优;否则上传更优。下面会给出证明;
其实关于第二种情况的证明,貌似没有题解真正给出一个合理的解释。我个人觉得下面的证明更加有力:
第一个方面同第一篇题解,就是如果这个节点剩余最小的军队(设为 \(A\))去分配,则只能分配给边权比该节点的边权还要小的结点,而这些结点让这个结点其他的军队去匹配照样没问题。但这里并没有说明,为什么让其他结点的军队来入驻会不优。
我们来考虑,如果让其他结点的军队来入驻,那一定是剩余时间大于该结点对应边权的军队,这些军队都比 \(A\) 上传后能匹配更多的结点。所以,让 \(A\) 留下可以增加那些军队匹配其他结点的机会。
那么接下来就好办了。我们把能上传的结点减掉消耗的边权后扔进 multiset 里,然后对于每个没有匹配的结点,按照边权找到第一个大于的军队来匹配就好。
代码(又丑又乱,凑合看吧 xwx):
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+100;
#define ll long long
inline int read(){
int x = 0; char ch = getchar();
while(ch<'0' || ch>'9') ch = getchar();
while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
return x;
}
int head[N], tot;
struct node{
int nxt, to, w;
}edge[N<<1];
void add(int u, int v, int w){
edge[++tot].nxt = head[u];
edge[tot].to = v;
edge[tot].w = w;
head[u] = tot;
}
int dep[N]; ll dst[N];
int cnt[N];
int n;
int m;
int lg[N];
int fa[N][16];
bool not_leaf[N];
void predfs(int u, int fath){
fa[u][0] = fath;
for(int i = 1; i<=lg[dep[u]]; ++i){
fa[u][i] = fa[fa[u][i-1]][i-1];
}
bool flag = 0;
for(int i = head[u]; i;i = edge[i].nxt){
int v = edge[i].to;
if(v == fath) continue;
flag = 1;
dep[v] = dep[u] + 1;
dst[v] = dst[u] + edge[i].w;
predfs(v, u);
}
not_leaf[u] = flag;
}
bool vis[N];
vector<long long> vc[N];
long long mxdst;
void dfs(int u, int fath){
if(cnt[u]){
int tmp = u;
long long rsd = mxdst;
for(int i = lg[dep[u]]; i>=0; --i){
if(fa[tmp][i] != 1 && dst[tmp] - dst[fa[tmp][i]] <= rsd){
rsd-= (dst[tmp] - dst[fa[tmp][i]]);
tmp = fa[tmp][i];
i = lg[dep[tmp]];
}
}
if(fa[tmp][0] == 1){
for(int i = 1; i<=cnt[u]; ++i){
vc[tmp].push_back(rsd);
}
} else{
vis[tmp] = 1;
}
}
for(int i = head[u]; i; i = edge[i].nxt){
int v = edge[i].to;
if(v == fath) continue;
dfs(v, u);
}
}
vector<int> single_p;
void dfs2(int u, int fath){
if(vis[u]) return;
bool flag = not_leaf[u];
// if(fath == 1 && !head[u]) flag = 0;
for(int i = head[u]; i; i = edge[i].nxt){
int v = edge[i].to;
if(v == fath) continue;
dfs2(v, u);
flag&=vis[v];
}
vis[u] = flag;
}
// priority_queue<long long> q;
multiset<long long> s;
bool check(ll x){
mxdst = x;
memset(vis, 0, sizeof(vis));
// for(int i = head[1]; i; i = edge[i].nxt){
// vc[edge[i].to].clear();
// }
single_p.clear();
s.clear();
dfs(1, 0);
dfs2(1, 0);
for(int i = head[1]; i; i = edge[i].nxt){
int v = edge[i].to;
if(vc[v].size()){
if(vis[v]){
for(auto j:vc[v]){
if(j > dst[v]) s.insert(j - dst[v]);
}
} else{
sort(vc[v].begin(), vc[v].end());
int st = 0;
if(vc[v][0]< 2*dst[v]) st = 1;
for(int j = st; j<(int)vc[v].size(); ++j){
if(vc[v][j] > dst[v]) s.insert(vc[v][j] - dst[v]);
}
if(!st)single_p.push_back(v);
}
vc[v].clear();
} else{
if(!vis[v]) single_p.push_back(v);
}
}
multiset<long long >::iterator it;
for(auto u: single_p){
it = s.lower_bound(dst[u]);
if(it == s.end()){
return 0;
}
s.erase(it);
}
return 1;
}
int main(){
// freopen("data.txt", "r", stdin);
// freopen("my.out", "w", stdout);
n = read();
for(int i = 2; i<=n; ++i){
lg[i] = lg[i>>1] + 1;
}
int cntrt = 0;
for(int i = 1; i<n; ++i){
int u =read(), v = read(), w = read();
add(u, v, w);
add(v, u, w);
if(u == 1 || v == 1) ++cntrt;
}
m = read();
for(int i = 1; i<=m; ++i){
++cnt[read()];
}
if(m < cntrt){
puts("-1");
return 0;
}
predfs(1, 0);
ll l = 0, r = 500000000;
ll ans = 0;
while(l <= r){
// puts("1");
ll mid = (l+r)>>1;
if(check(mid)){
r = mid-1;
ans = mid;
} else{
l = mid+1;
}
}
printf("%lld\n", ans);
return 0;
}