[NOIP2012 提高组] 疫情控制 题解
[NOIP2012 提高组] 疫情控制
题意:
给定一棵树,边有边权,有一些结点上有军队(可能不止一支),军队可移动。求最短的时间,使得军队移动后,从根到每个叶子结点的路径上都有军队驻扎。军队可以同时移动。
思路:
咳咳咳我当时读错题了以为这题虚高,然后才意识到边境结点只有叶子结点。
首先一个很明显的贪心就是,军队能往根移动就一定会往根移动,因为深度浅的结点能控制的叶子结点一定不少于深度深的结点。
然后我就不会了,看了题解。咳咳咳,然后既然要求最短的时间,想到二分。这题的重点就是二分后的 check 的写法。
首先还是像刚才的贪心,让军队能往上走就往上走,走的过程用倍增加速。如果有的军队能够走到根节点的子节点,那么就记录下来剩下的时间还有多少;走不到的,就对终点打上标记,代表这个子树已经被封死了。注意根节点的子节点很特殊!,要特殊处理,所以就不打标记。这是第一遍 dfs。
然后是第二遍 dfs,这一遍是在处理子树是否被封死,分以下几种情况:
- 是叶子,但没有标记,则未被封死。
- 有标记,且不是根节点的子节点,被封死。
- 不是叶子,没有标记,但所有的儿子都被封死,被封死。
这样处理完后,我们就得到了根结点儿子的所有信息了。最后一步,就是分配军队。下面所说的上传军队,都是在把剩余时间大于到根的距离的军队上传。
对于一个根节点的子节点,也要分情况:
- 如果被封死,则所有的军队都可以上传;
- 如果没被封死,如果最小的军队的剩余时间不足以从该结点走上根节点再走下来,则留驻更优;否则上传更优。下面会给出证明;
其实关于第二种情况的证明,貌似没有题解真正给出一个合理的解释。我个人觉得下面的证明更加有力:
第一个方面同第一篇题解,就是如果这个节点剩余最小的军队(设为
我们来考虑,如果让其他结点的军队来入驻,那一定是剩余时间大于该结点对应边权的军队,这些军队都比
那么接下来就好办了。我们把能上传的结点减掉消耗的边权后扔进 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; }