题目描述
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,
也是树中的根节点。
H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市
(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市
的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首
都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可
以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个
城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道
路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
输入格式
第一行一个整数 n,表示城市个数。
接下来的 n-1 行,每行 3 个整数,u,v,w,每两个整数之间用一个空格隔开,表示从城市 u
到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。
接下来一行一个整数 m,表示军队个数。
接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎的
城市的编号。
输出格式
一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
题解
这道题是我2020秋季数据结构课程设计的题目,思来想去不会做,在洛谷,csdn借鉴各位大佬的思路后,勉强通过了课设,现贴代码及思路如下,希望各位大佬批评指正。
该题要解决的问题是在除首都以外的任意一个城市驻扎军队,让每支军队在二分的时间内尽可能多的控制城市,让军队尽量往首都移动(军队越往上,能封住的城市越多),一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度,即要在最短的时间内控制住疫情;总体思路是树上倍增+预处理+贪心。
参考了这位大神的代码: https://blog.csdn.net/ly3098268698/article/details/107289320
我的设计思想:
源代码如下:
const int MaxSize=1000;
const int nsize=20;
int city_number;//城市数
int army_number;//军队数
int total=0;//边的数量*2
int help_army_number=0;//调整后可用军队数
int need_city_number=0;//调整后仍需军队驻扎的节点数
int pre_army_number=0;//调整前到达根节点的军队数
int v_value[2*MaxSize];//v_value[tot]:存储第total次add的v值
int weight[2*MaxSize];//weight[total]:存储第total次add的w值
int Next[2*MaxSize];//Next[total]:存储第total次add的u值上一次的total'值
int last[MaxSize];//last[u]:存储u值上一次add对应的total值
int army_loc[MaxSize];//army_loc[i]:第i号军队的军队位置
int city_deep[MaxSize];//city_deep[i]:第i号城市的深度
int number[MaxSize][20];//number[i][j]:第i个节点的第2^j个祖先节点编号
int dist[MaxSize][20];//dist[i][j]:第i个节点到它第2^j个节点的路径距离
int left_time=0;//二分搜索的初始左边界
int right_time=0;//二分搜索的初始右边界
bool army_stay[MaxSize];//army_stay[i]:i号城市是否有军队驻扎,有则army_stay[i]=1,否则army_stay[i]=0
bool need_army[MaxSize];//need_army[i]:i号城市需要军队驻扎,need_army[i]=1,否则need_army[i]=0
int army_root_dist[MaxSize];//army_root_dist[i]:重新调整军队后仍停留在根节点的军队的可移动距离,但未记录这个军队从哪里来的
int unstay[MaxSize];//unstay[i]:城市i在重新调整军队后仍然处于无军队驻扎,unstay[i]=1,否则unstay[i]=0
void add_edge(int u,int v,int w)//添加一条边(u,v,w),表示从城市u到城市v有一条长为w的双向道路
{
total++;
v_value[total]=v;
weight[total]=w;
Next[total]=last[u];
last[u]=total;
}
void DFS()//用深度优先搜索方法构造dist数组和number数组,需要用到一个栈
{
SeqStack<int> S;//顺序栈
S.Push(1);
city_deep[1] = 1;
while(!S.IsEmpty())
{
int u;
S.Pop(u);
for(int i=last[u];i;i=Next[i])
{
int v=v_value[i];
if(!city_deep[v])
{
city_deep[v]=city_deep[u]+1;
number[v][0]=u;//number[i][j]:第i个节点的第2^j个祖先节点编号
dist[v][0]=weight[i];//dist[i][j]:第i个节点到它第2^j个节点的路径距离
for(int j=1;j<nsize;j++)//深度优先搜索构建number数组和dist数组,并利用倍增法进行优化
{
number[v][j]=number[number[v][j-1]][j-1];
dist[v][j]=dist[v][j-1]+dist[number[v][j-1]][j-1];
}
S.Push(v);
}
}
}
}
bool DFS(int u)
{
bool Is_leaf=0;//判断当前节点是否为叶子节点(边境城市)
if(army_stay[u]){return 1;}//若当前节点已被驻扎,则返回1
for(int i=last[u];i;i=Next[i])//遍历城市u的出边
{
int v=v_value[i];
if(city_deep[v]<city_deep[u])//遇到父亲节点
{
continue;
}
Is_leaf=1;//若有一条不是连接着父亲节点的边,说明不是叶子节点
if(!DFS(v)){return 0;}//若某个子节点搜索时遇到路径未被驻扎的叶子节点(边境城市),直接返回0
}
if(!Is_leaf){return 0;}//当前节点是叶子节点且未被驻扎
return 1;//没有遇到路径未被驻扎的叶子节点,返回1
}
bool check(int time)
{
pair<int,int> p[MaxSize];//p[i].first:该军队到达根节点后还能够走多远;p[i].second:该军队从根节点的哪个孩子节点来
for(int i=0;i<maxSize;i++)//初始化数组
{
army_stay[i]=0;
army_root_dist[i]=0;
unstay[i]=0;
p[i].first=0;
p[i].second=0;
need_army[i]=0;
}
for(int i=1;i<=army_number;i++)//沿当前节点上移军队,尽最大努力到达根节点(首都),若无法到达根节点,则停留在最浅节点
{
int u=army_loc[i],cnt=0;
for(int j=nsize;j>=0;j--)
{
if(number[u][j]>1 && cnt+dist[u][j]<=time)
{
cnt+=dist[u][j];
u=number[u][j];
}
}
if(number[u][0]==1 && cnt+dist[u][0]<=time)//调整前到达根节点的军队
{
p[++pre_army_number]=make_pair(time-cnt-dist[u][0],u);
}
else
{
army_stay[u]=1;//第u号城市有军队驻扎
}
}
for(int i=last[1];i!=0;i=Next[i])//深度优先搜索寻找路径上未被驻扎的叶子节点
{
if(!DFS(v_value[i]))
{
need_army[v_value[i]]=1;
}
}
sort(p+1,p+pre_army_number+1);
for(int i=1;i<=pre_army_number;i++)//对根节点的需要被驻扎的孩子节点进行初步处理
{
if(need_army[p[i].second] && p[i].first<dist[p[i].second][0])
{
need_army[p[i].second]=0;
}
else
{
army_root_dist[++help_army_number]=p[i].first;
}
}
for(int i=last[1];i;i=Next[i])//找到仍需要被驻扎的节点并存储
{
if(need_army[v_value[i]])
{
unstay[++need_city_number]=dist[v_value[i]][0];
}
}
if(help_army_number<need_city_number)//调用后可用军队数小于仍需驻扎城市数
{
return 0;
}
sort(army_root_dist+1,army_root_dist+help_army_number+1);
sort(unstay+1,unstay+need_city_number+1);
int i=1,j=1;
while(i<=need_city_number && j<=help_army_number)
{
if(army_root_dist[j]>=unstay[i]){i++;j++;}
else{j++;}
}
if(i>need_city_number)
{
return 1;
}
return 0;//利用贪心策略完成最后的分配
}
int main()
{
int loc;//军队所驻扎的城市编号
cin>>city_number;//输入城市个数
int child=0;//不包含首都的非边境城市个数
for(int i=1;i<city_number;i++)//输入u,v,w,表示从城市u到城市v有一条长为w的双向道路
{
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
add_edge(v,u,w);
if(u==1 || v==1){child++;}
right_time +=w;//二分搜索初始右边界
}
DFS();
cin>>army_number;//输入军队个数
for(int i=1;i<=army_number;i++)//构造军队初始信息
{
cin>>loc;
army_loc[i]=loc;//第i号军队的位置
army_stay[i]=1;
}
if(army_number<child)//军队个数小于非边境城市个数(不包含首都),则无法控制疫情
{
cout<<-1<<endl;
return 0;
}
int Min_time=0;//最终时间
while(left_time<=right_time)//二分答案
{
int mid=(left_time+right_time)/2;
if(check(mid))
{
right_time=mid-1;
Min_time=mid;
}
else
{
left_time=mid+1;
}
}
cout<<Min_time<<endl;
}
PS:我用到的是自己写的一个栈,用编译器自带的栈更好