[NOIP2012 提高组] 疫情控制 题解

发布时间 2023-08-31 15:16:06作者: 霜木_Atomic

[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;
}