题目描述
HH 国有 n n 个城市,这 nn 个城市用 n-1 n−1 条双向道路相互连通构成一棵树, 1 1 号城市是首都,也是树中的根节点。
H H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 HH 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
输入输出格式
输入格式:
第一行一个整数 nn ,表示城市个数。
接下来的 n-1n−1 行,每行 3 3 个整数, u,v,wu,v,w ,每两个整数之间用一个空格隔开,表示从城市 u u 到城市 vv 有一条长为 ww 的道路。数据保证输入的是一棵树,且根节点编号为 11 。
接下来一行一个整数 mm ,表示军队个数。
接下来一行 m m 个整数,每两个整数之间用一个空格隔开,分别表示这 mm 个军队所驻扎的城市的编号。
输出格式:
一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出 -1−1 。
输入输出样例
输入样例#1:
4
1 2 1
1 3 2
3 4 3
2
2 2
输出样例#1:
3
说明
【输入输出样例说明】
第一支军队在 22 号点设立检查点,第二支军队从 22 号点移动到 33 号点设立检查点,所需时间为 33 个小时。
【数据范围】
保证军队不会驻扎在首都。
对于 100%的数据, 2≤m≤n≤50,000,0< w <10^9
【题解】
这道题实在是恶心。。
我想出算法后,在码程序的过程中又出现了很多细节问题。。
算法:二分+贪心+倍增
题意:用m个点“封死”一棵树
- 贪心思想:离根节点越近的点更优
- 二分:改题的答案满足单调性,也是求最值的问题,当然用二分
- 倍增:发现n≤50000,在树上的移动需要用到倍增
那么如何使用这些算法?
- 二分答案
- 在二分的check函数中,首先上提军队,在mid的范围内,把军队往根节点提(因为贪心思想),军队按此操作会出现两种情况:
1)到不了根节点,那么标记该军队能到的最远的地方
2)能到根节点,把这些军队放到另外一个数组里,因为这些军队可以可能跑到其他地方去更优 - 遍历整棵树,给子树都被“封死”的点打标记
- 把深度为2的并没被封死的点拎出来,它们就需要刚才那些到根节点的军队的帮助了
- 看看军队是否可以覆盖剩余的点
Code:
#include <bits/stdc++.h>
#define ll long long
#define res register int
#define maxn 50010
using namespace std;
struct Node1{
int to,next,dis;
};
struct Node2{
int num,dis;
};
Node1 edge[maxn<<1];
Node2 a[maxn],b[maxn];
int head[maxn<<1],d[maxn],fa[maxn][30],dis[maxn][30],w[maxn],n,m,num;
bool vis[maxn];
inline int read(){
int s=0,w=1;
char c=getchar();
while (c<'0' || c>'9'){if (c=='-')w=-1;c=getchar();}
while (c>='0' && c<='9') s=s*10+c-'0',c=getchar();
return s*w;
}
inline void add(int x,int y,int z){edge[++num].to=y;edge[num].next=head[x];edge[num].dis=z;head[x]=num;}
inline void build(int u,int pre,int W){ //倍增初始化
d[u]=d[pre]+1,fa[u][0]=pre,dis[u][0]=W;
for (res i=0;fa[u][i];++i) fa[u][i+1]=fa[fa[u][i]][i],dis[u][i+1]=dis[u][i]+dis[fa[u][i]][i];
for (res i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if (v!=pre) build(v,u,edge[i].dis);
}
}
inline void dfs(int u){//打标记
bool p=1,q=0;
for (res i=head[u];i;i=edge[i].next){
int v=edge[i].to; if (v==fa[u][0]) continue;
dfs(v);
p&=vis[v],q=1;
}
if (u!=1 && p && q) vis[u]=1;
}
inline bool cmp(Node2 a1,Node2 a2){return a1.dis<a2.dis;}//其实是多余的。。
inline bool check(int Tmp){//check函数
int num1=0,num2=0; memset(vis,0,sizeof(vis));
for (res i=1;i<=m;++i){
int x=w[i],sum=0;
for (res j=20;j>=0;--j) if (sum+dis[x][j]<=Tmp && fa[x][j]) sum+=dis[x][j],x=fa[x][j];
if (x!=1) vis[x]=1;
else{
x=w[i],a[++num1].dis=Tmp-sum;
for (res j=20;j>=0;--j) if (fa[x][j]>1) x=fa[x][j];
a[num1].num=x;
}
}
dfs(1);
for (res i=head[1];i;i=edge[i].next){
int v=edge[i].to;
if (!vis[v]) {
b[++num2].num=v;
b[num2].dis=edge[i].dis;
}
}
sort(a+1,a+1+num1,cmp); sort(b+1,b+1+num2,cmp); res j=1;
for (res i=1;i<=num1;++i){
if (!vis[a[i].num]) vis[a[i].num]=1;
else if (a[i].dis>=b[j].dis) vis[b[j].num]=1;
while (vis[b[j].num] && j<=num2) ++j;
}
return j>num2;
}
int main(){
n=read(); int r;
for (res i=1;i<n;++i){
int x=read(),y=read(),z=read();
add(x,y,z); add(y,x,z); r+=z;
}
build(1,0,0);
m=read();
for (res i=1;i<=m;++i) w[i]=read();
int CNT=0;
for (res i=1;i<=n;++i) if (d[i]==2) ++CNT;
if (CNT>m){printf("-1");return 0;}
int l=0,ans=-1;
while (l<=r){
int mid=(l+r)>>1;
if (check(mid)) ans=mid,r=mid-1;else l=mid+1;
}
printf("%d\n",ans);
return 0;
}