版权声明:本文为DyingShu原创文章,转载请注明出处哦。 https://blog.csdn.net/DyingShu/article/details/82055522
一道很全(du)面(liu)的题
题目大意:给定一棵树,用最少的时间封住这棵树。
题解:
首先可以很容易发现一个条件:军队在走的时候都要尽量往上走,但不到根节点。因为越靠近根节点的点,控制的叶子节点越多。不过暴力是肯定会超时的,用倍增优化。
题目求的是最长移动时间军队的最短时间,想到二分答案。
但是还有这样一种情况:一支军队到达首都之后,又到达了另一个城市驻扎。
对于这种情况,我们需要记录所有到了首都之后还有剩余距离的军队,记录编号和剩余距离。
不能到达首都的军队,就封死自己最高能到的点就行了
然后再DFS一次,记录还没有被封死的,根节点的子树,记录距离。按距离给这些子树从大到小排序。
对于这些子树,如果有经过它到首都的军队,就取其中剩余距离最短的;如果没有,就取剩余路程最长的。
重(keng)点:
- 排序后的编号与原编号不同,要记录一个原编号
- 若一个军队只能在叶子节点,则该叶子节点也算被封死(主要是我写的时候顺序反了)
- 若到达首都时剩余距离为0,则算作不能到首都
- Check()大毒瘤,其他都是板子(233)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 50001;
int n, m, Army[MAXN], Now_Army[MAXN];
int fir[MAXN], nxt[MAXN << 1], to[MAXN << 1], len[MAXN << 1], cnt;
int f[MAXN][18], dep[MAXN], du[MAXN], flag[MAXN], leaf[MAXN];
LL dis[MAXN], Min[MAXN];
int Color[MAXN], Need[MAXN], tag[MAXN];
struct Node{
LL dis; int num;
}q[MAXN];
inline int read(){
int k = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
return k*f;
}
inline void add_edge(int a, int b, int l){
len[cnt] = l, to[cnt] = b;
nxt[cnt] = fir[a], fir[a] = cnt++;
}
inline bool cmp1(Node a, Node b){
return a.dis > b.dis;
}
inline bool cmp2(int a, int b){
return dis[a] > dis[b];
}
void dfs(int u, int fa){
f[u][0] = fa;
for(int i = 1; (1 << i) <= dep[u]; i++){
f[u][i] = f[f[u][i - 1]][i - 1];
}
for(int i = fir[u]; i != -1; i = nxt[i]){
int v = to[i];
if(v == fa) continue;
dep[v] = dep[u] + 1;
dis[v] = dis[u] + len[i];
du[u]++;
dfs(v, u);
}
if(du[u] == 0) leaf[u] = true;
}
int Jump(int Cur, LL d){
for(int j = 17; j >= 0; j--){
if(f[Cur][j] > 1 && d - (dis[Cur] - dis[f[Cur][j]]) >= 0){
d -= (dis[Cur] - dis[f[Cur][j]]);
Cur = f[Cur][j];
}
}
return Cur;
}
bool dfs2(int u, int fa){
if(flag[u]) return false; //注意!!!这两个标记的优先度不同!!!
if(leaf[u]) return true;
for(int i = fir[u]; i != -1; i = nxt[i]){
int v = to[i];
if(v == fa) continue;
if(dfs2(v, u)) return true;
}
return false;
}
bool Check(LL tim){
memset(flag, false, sizeof(flag)); q[0].dis = Need[0] = 0;
memset(Color, 0, sizeof(Color));
for(int i = 1; i <= n; i++) flag[i] = Color[i] = 0;
for(int i = 1; i <= m; i++) tag[i] = 0;
for(int i = 1; i <= m; i++){
int Top = Jump(Army[i], tim); //向上跳,但不可到根节点
if(dis[Army[i]] >= tim){
flag[Top] = true;
}
else{
q[++q[0].dis].dis = tim - dis[Army[i]]; //来首都后还能走多远
q[q[0].dis].num = i;//来首都的时候就经过的城市
if(!Color[Top] || q[q[0].dis].dis < Min[Top]){
Min[Top] = q[q[0].dis].dis, Color[Top] = i;
}
}
}
if(!dfs2(1, 0)) return true;
for(int i = fir[1]; i != -1; i = nxt[i]){
int v = to[i];
if(dfs2(v, 1)){
Need[++Need[0]] = v; //需要支援的城市
}
}
sort(q + 1, q + q[0].dis + 1, cmp1);
sort(Need + 1, Need + Need[0] + 1, cmp2);
int Cur = 1;
for(int i = 1; i <= Need[0]; i++){
if(!tag[Color[Need[i]]]){ //如果有来过这个城市的军队
tag[Color[Need[i]]] = true; //选这个最小的
continue;
}
while(tag[q[Cur].num] && Cur <= q[0].dis) Cur++; //已经使用
if(Cur > q[0].dis) return false; //没军队了
//从首都调军队
if(q[Cur].dis < dis[Need[i]]) return false;
tag[q[Cur].num] = true;
}
return true;
}
int main(){
memset(fir, -1, sizeof(fir));
n = read();
for(int i = 1; i < n; i++){
int a = read(), b = read(), l = read();
add_edge(a, b, l), add_edge(b, a, l);
}
dep[1] = dis[1] = 0;
dfs(1, 0);
m = read();
for(int i = 1; i <= m; i++){
Army[i] = read();
}
int Temp = 0;
for(int i = fir[1]; i != -1; i = nxt[i]) Temp++;
if(m < Temp){printf("-1"); return 0;}
long long l = 0, r = 1e14, mid;
while(l < r){
mid = (l + r) >> 1;
if(Check(mid)) r = mid;
else l = mid + 1;
}
printf("%lld", l);
return 0;
}