思路
我们的方法是:二分+贪心+倍增。
我们可以知道:如果这个时间可以让题意满足,则比它大的时间也可以让题意满足。
所以有单调性,我们就可以二分答案。
但 c h e c k check check函数怎么打呢?我们要使用贪心:显然我们希望每个军队都停留在深度更小的节点(从而管理更多的路径),所以我们就需要军队向上走。
若一个军队可以走到根节点的子节点,则令其暂时停在根节点的子节点。
否则走到能够走到的深度最小的节点。
可以使用树上倍增进行优化。
对于跳得到根节点的子节点军队,我们有两个选择:
对于跳至根的子节点后仍有剩余时间的军队 s s s,分成两种情况:
1.如果剩余时间不能使其在根节点之间跳一个往返,即剩余时间小于 2 × d i s t ( s , r o o t ) 2×dist(s,root) 2×dist(s,root)的军队,原地驻扎。
2.将符合此条件的所有军队按照剩余时间排序,并将还未控制住疫情的根的子节点按照距根的距离排序,用双指针将军队与城市一一进行匹配。这些军队按照剩余时间从小到大排序,然后和上一步处理出来的这些节点一一进行匹配。这是一个可以证明正确性的贪心策略。若所有未被驻扎的节点都有军队驻扎,则说明当前的时间限制可行;反之则不可行。
考虑这么贪心做的正确性,对于剩余时间不够的军队,如果选择跳过树根去另一个子节点 s ′ s′ s′驻扎,则必然 d i s t ( r o o t , s ) > d i s t ( r o o t , s ′ ) dist(root,s)>dist(root,s′) dist(root,s)>dist(root,s′),这么做可能会导致需要一个剩余时间足够的军队 s ′ ′ s′′ s′′从其本来位置跨过根跳至 s s s,花费时间 d i s t ( r o o t , s ′ ′ ) + d i s t ( r o o t , s ) dist(root,s′′)+dist(root,s) dist(root,s′′)+dist(root,s),而这样做花的时间显然比 s s s原地驻扎, s ′ ′ s′′ s′′跨过根跳至 s ′ s′ s′的情况长,于是贪心策略正确。
当然,也有可能存在一个军队剩余时间不够,但是其子树已被另一个军队控制的情况,那么这个军队就应算入到上文的第二种情况中。
输入
void add_edge(int u,int v,int w) {
ver[++cnt]=v,edge[cnt]=w,next[cnt]=head[u],head[u]=cnt; }
scanf("%d",&n);
for(int i=1,u,v,w;i<n;i++) {
scanf("%d %d %d",&u,&v,&w);
add_edge(u,v,w),add_edge(v,u,w);
sum+=w;
}
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
(打着水博客的心思写了上来)
二分
int l=1,r=sum,ans;
bool flag=