题面
\quad 大体思路: 因为用更多的时间肯定也可以完成要求,
所以满足单调性,所以考虑二分答案。对于每次的答案 m i d mid mid,把所有
军队在 m i d mid mid的时间内尽量往上提,可以用倍增在单次 O ( l o g n ) O(logn) O(logn)的时间里做到。如果能到达根节点,往 s e t set set中插入该军队到根节点后还能移动的距离(下文称之为可移动距离),这些军队可以进驻在自身的可移动距离内的任意节点。
\quad 所有节点都尽量向上移动后,统计根节点各个子节点是否需要军队驻扎。如果某个子节点 a a a没有军队上到根节点且需要军队,那么直接在set中找到可移动距离尽量小但能到达 a a a的军队驻扎并在 s e t set set中删除该军队。
\quad 如果某个节点 b b b有军队到了根节点但是需要驻扎,我们设根到该节点距离为 w w w,该点往 s e t set set中插入的的军队的可移动距离为 v v v(有多个取较小值),我们讨论一下:
w > v w>v w>v,如果用根节点储存的军队驻扎,就是用大于 w w w的值变成 v v v明显不划算,所以我们直接"撤回",让这个节点的军队退回去,在set中删除该军队。
w < v w
\quad 如果所有根节点子节点都有驻扎,那么这个 m i d mid mid是合法的。总的时间复杂度是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。常数略大,仅供娱乐。
另外如果军队数小于根节点的子节点数,那么无解,输出-1。
// luogu-judger-enable-o2
#include
using namespace std;
#define MN 50005
#define INF 1e16
#define Lg 17
#define LL long long
#define itset multiset::iterator
inline int max(int a,int b){if(a>b)return a;return b;}//提醒一下,三目运算符速度比if……else快,但是没有1个if快
inline int read(){
int a=0;char c=getchar();
while(c>57 or c<48)c=getchar();
while(47<c and c<58){
a=a*10+c-48;
c=getchar();
}
return a;
}
struct Edge{
int to,v;
Edge(int TO,int V){to=TO;v=V;}
};//vector建边的结构体
vector<Edge>edge[MN];
multiset<LL>cnt;//记录根节点储存的节点
int fa[Lg][MN],tag[MN],n,m,u,v,w,loc[MN];
LL UP[MN],cost[Lg][MN],lon[MN];
//fa、cost:倍增的父亲数组和花费的时间
//loc:军队初始位置
//tag:这个节点是否有军队
//UP:从这个节点上到根节点的军队的可移动距离
//lon:一棵树的最长链
void dfs(int x){
for(int i=1;i<Lg;++i){
fa[i][x]=fa[i-1][fa[i-1][x]];
cost[i][x]=cost[i-1][x]+cost[i-1][fa[i-1][x]];
}//倍增预处理
for(int i=0;i<edge[x].size();++i)
if(edge[x][i].to!=fa[0][x]){
fa[0][edge[x][i].to]=x;
cost[0][edge[x][i].to]=edge[x][i].v;
dfs(edge[x][i].to);
lon[x]=max(lon[x],lon[edge[x][i].to]+(LL)edge[x][i].v);
}
}
void up(int x,LL T){//尽量向上跳
int tmp=x;
for(int i=Lg-1;i>=0;--i)
if(cost[i][x]<=T&&fa[i][x]>1){
//要记录由哪个节点跳到1,所以先不能跳到1
T-=cost[i][x];x=fa[i][x];
//注意这里!先减花费的时间再跳向父亲!
}
if(T<cost[0][x]){tag[x]=1;return;}//跳不了了,打个标记
UP[x]=min(UP[x],T-cost[0][x]);
//记录由这个节点跳上去的军队的最小的可移动距离
cnt.insert(T-cost[0][x]);
//插入该军队的可移动距离
}
void DFS(int x){
if(tag[x])return;//该节点有军队,不用进驻了
if(edge[x].size()!=1)tag[x]=1;//size等于1就是叶子节点
for(int i=0;i<edge[x].size();++i)
if(edge[x][i].to!=fa[0][x]){
DFS(edge[x][i].to);
if(!tag[edge[x][i].to]){
tag[x]=0;//子节点都有军队,那么也不用进驻了
}
}
}
bool chk(LL T){
cnt.clear();
memset(tag,0,sizeof(tag));
memset(UP,0x7f7f7f,sizeof(UP));
for(int i=1;i<=m;++i){
up(loc[i],T);//跳
}
DFS(1);//统计
for(int i=0;i<edge[1].size();++i){
if(!tag[edge[1][i].to]){
if(UP[edge[1][i].to]<edge[1][i].v){
cnt.erase(cnt.find(UP[edge[1][i].to]));
tag[edge[1][i].to]=1;
}
}//直接把这个军队撤回来
}
for(int i=0;i<edge[1].size();++i){
if(!tag[edge[1][i].to]){
itset IT=cnt.lower_bound(edge[1][i].v);
if(IT==cnt.end()) return 0;//找不到符合要求的军队,不合法
cnt.erase(IT);
}
//上下2个操作不能同时进行,不然可能把需要撤回的军队拿去驻扎了,比较麻烦
}
return 1;
}
signed main(){
n=read();
for(int i=1;i<n;++i){
u=read();v=read();w=read();
edge[u].push_back(Edge(v,w));
edge[v].push_back(Edge(u,w));
//建边
}
dfs(1);
m=read();
for(int i=1;i<=m;++i)
loc[i]=read();
LL l=-1;
LL r=lon[1]<<1;//最大需要的时间是最长链的2倍
while(l+1<r){
LL mid=(l+r)>>1;
if(chk(mid)) r=mid;
else l=mid;
}//左开右闭的奇葩二分小心品尝。。。
printf("%lld",r);
return 0;
}