细节比较多,思维难度还是有的(主要是考虑情况的完整性),可能重点在实现上面吧。
Solution:
最少需要多少? 很明显的可以二分答案是吧。
然后重点考虑如何check。可以来模拟一下这个过程:
首先,一个很显然的贪心策略: 一支军队能往上走就尽量往上走。
由于现在知道每支军队能够向上走多少步,
所以可以用树上倍增,把每支军队跳到根节点下或停在它不能继续向上的位置。
这个时候进行一次统计,看还有多少个叶子节点没有被覆盖的掉,记录一下它的距离根最近的祖先,即根的某个儿子。
为什么记录它是很显然的:它所在的子树还有未被覆盖的叶子节点,那么必然需要其他子树中的过来,显然走到它就停下是最优的。
无解的情况:记录的点数>多余的军队数。
然后,
I:会了会了,直接把所有这样的根的儿子找出来后,再统计一下其他子树有多少可以空出来。
都用个数组存着,从小到大排个序,用剩余时间少的军队去距离近的子树,只要保证其他子树至少有一个就好了!
很对很对恩。。
码码码。。。。
I:Wocao,60!怎么回事⊙ o ⊙ 。
如果您也在纳闷,那么不妨看这样一组数据:
Input:
4
1 2 9
1 3 1
1 4 2
3
1 1 2
Output:
10
如果您犯了这个错误,应该已经明白问题在哪了。
所以说这个地方还是很容易成为一个思维漏洞的,这应该也是此题最关键的地方了。
这个时候我们需要遵循一个贪心策略 :
如果一些满足上述条件的军队Ai(都在S处),其中最小的不能满足他剩余的距离能够到root又回来,即小于2*dis(root,S);
那么必须留下那个剩余时间最短的军队守S,其余的当做跨根的用,否则可以都当做跨跟的用。
至此,我们就可以开心的解决这个问题了,代码实现过程中可能需要注意一下下吧。
Code↓:
// luogu-judger-enable-o2
#include<queue>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
IL int gi() {
char ch=getchar(); RG int x=0,w=0;
while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=1e5+10;
queue<int> q;
vector<LL> s[N];
LL L,R=1e15,mid,d[N][21];
int n,m,Lef,num,cnt,PaPa,tot,head[N],army[N],f[N][21],vis[N],tag[N],leaf[N],Pa[N],occ[N];
struct ARMY{int bel;LL res;}A[N],Need[N];
struct EDGE{int next,to,v;}e[N<<1];
IL bool CMP(ARMY a,ARMY b) {return a.res<b.res;}
IL void make(int a,int b,int c) {
e[++tot]=(EDGE){head[a],b,c},head[a]=tot;
e[++tot]=(EDGE){head[b],a,c},head[b]=tot;
}
void dfs(int x,int fx) {
RG int i,y,fl=0;
for (i=head[x];i;i=e[i].next)
if ((y=e[i].to)!=fx) {
if (x==1) PaPa=y;
fl=1,f[y][0]=x,d[y][0]=e[i].v,dfs(y,x);
}
if (!fl) leaf[++Lef]=x,Pa[Lef]=PaPa;
}
IL void work() {
RG int i,j,p;
for (i=1;i<=17;++i)
for (j=1;j<=n;++j)
p=f[j][i-1],f[j][i]=f[p][i-1],d[j][i]=d[j][i-1]+d[p][i-1];
}
IL void BFS(int S) {
RG int i,x,y;
q.push(S);
while (!q.empty()) {
x=q.front(),q.pop();
for (i=head[x],vis[x]=1;i;i=e[i].next)
if ((y=e[i].to)!=f[x][0]&&!vis[y]) q.push(y);
}
}
IL int check() {
RG LL sum;
RG int i,j,x;
memset(occ,0,sizeof(occ));
memset(vis,0,sizeof(vis));
memset(tag,0,sizeof(tag));
for (i=head[1];i;i=e[i].next) s[e[i].to].clear();
for (i=1,cnt=0,num=0;i<=m;++i) {
for (j=17,x=army[i],sum=0;j>=0;--j)
if (f[x][j]>1&&sum+d[x][j]<=mid) sum+=d[x][j],x=f[x][j];
if (f[x][0]==1) s[x].push_back(mid-sum);
else if (!vis[x]) BFS(x);
}
for (i=head[1];i;i=e[i].next) sort(s[e[i].to].begin(),s[e[i].to].end());
for (i=1;i<=Lef;++i)
if (!vis[leaf[i]]&&!occ[x=Pa[i]]) {
occ[x]=1;
if (!s[x].size()||s[x][0]>=2*d[x][0]) Need[++num]=(ARMY){x,d[x][0]};
else tag[x]=1;
}
for (i=head[1];i;i=e[i].next)
for (x=e[i].to,j=tag[x];j<s[x].size();++j)
if (s[x][j]>d[x][0]) A[++cnt]=(ARMY){x,s[x][j]-d[x][0]};
sort(A+1,A+cnt+1,CMP);
sort(Need+1,Need+num+1,CMP);
for (i=1,j=1;i<=num;++i,++j) {
while (j<=cnt&&Need[i].res>A[j].res) ++j;
if (j==cnt+1) break;
}
return i>num;
}
int main()
{
RG int i,x,y,z;
for (i=1,n=gi();i<n;++i) x=gi(),y=gi(),z=gi(),make(x,y,z);
for (i=1,m=gi();i<=m;++i) army[i]=gi();
dfs(1,1),work();
while (L<R) {
mid=L+R>>1;
if (check()) R=mid;
else L=mid+1;
}
printf("%lld
",R);
return 0;
}
// 程序有点慢
// 一定要注意有一个很重要的点:
// 那就是如果只有一个军队A驻扎在S,S∈SON(root),那么有两种可能性
// 一另是A守住S这一棵子树,另一种是A去别的子树S',别的A'来S.
// 此时有一个结论需要遵守:
// 如果一些满足上述条件的军队Ai(都在S处),其中最小的不能满足他剩余的距离能够到root又回来,即小于2*dis(root,S);
// 那么必须留下那个剩余时间最短的军队守S,其余的当做跨根的用.