疫情控制

来源:互联网 发布:win7桌面显示网络图标 编辑:程序博客网 时间:2024/05/01 22:48

传送门:疫情控制

题目描述

H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,

也是树中的根节点。

H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境

城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境

城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,

首都是不能建立检查点的。

现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在

一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等

于道路的长度(单位:小时)。

请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

输入输出格式

输入格式:

第一行一个整数 n,表示城市个数。

接下来的 n-1 行,每行 3 个整数,u、v、w,每两个整数之间用一个空格隔开,表示从

城市 u 到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。

接下来一行一个整数 m,表示军队个数。

接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎

的城市的编号。

输出格式:

共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。

输入输出样例

输入样例#1:
4 1 2 1 1 3 2 3 4 3 2 2 2
输出样例#1:
3

说明

【输入输出样例说明】

第一支军队在 2 号点设立检查点,第二支军队从 2 号点移动到 3 号点设立检查点,所需

时间为 3 个小时。

【数据范围】

保证军队不会驻扎在首都。

对于 20%的数据,2≤ n≤ 10;

对于 40%的数据,2 ≤n≤50,0<w <10^5;

对于 60%的数据,2 ≤ n≤1000,0<w <10^6;

对于 80%的数据,2 ≤ n≤10,000;

对于 100%的数据,2≤m≤n≤50,000,0<w <10^9。

 

Algorithm:倍增,贪心,二分答案

Solution:首先从输出无解的方面考虑。

现在我们抛开所有时间不顾,假定现有x个点与首都相连,现在要求求出最少需要多少个军队才能控制疫情。根据控制疫情这道题里面的定义,我们可以把一个军队当做一条割线,即用多少条割线才能使所有的边境城市与首都分离。

显然我们最少是需要x个军队的,因为我们只需要把每个军队放在与首都相连的所有点上即可,因为这些点与首都之间的边是边境城市必经之道,证明如下:

现点u与首都相连,如果u为边境城市,因为在u上放了军队,所以此处的疫情得以控制;如果不是,因为该图是一棵树,因此其到根节点的路径唯一且必须经过一条与首都相连的边,因为在u到根节点之间离根节点最近的位置上放了军队,因此疫情不会扩散到u处。

得证。

现在考虑求得解的算法,显而易见,我们所需要派发的军队希望尽量地离首都近,因为军队离首都越近,可以拦截到的边境城市可能越多。但是走得越远时间越长。因此我们要考虑对时间的测定。假如当前时间t可以控制住疫情,那么对于时间s,0<=s<=t也很有可能控制住疫情,对于s>=t一定可以控制住疫情,因此我们可以二分时间,然后每一次对当前时间是否能够控制住疫情进行判断。我们现在对于当前时间t,让所有的军队都尽量地往根节点走(因为可能走到某些路径上的时候时间已经用完但是仍未到达当前边的目标点)。如果军队不能走到根节点,那我们对它当前走到的最大节点进行标记,即该节点的儿孙节点中所有是边境城市的疫情都可以控制住;如果军队走到了根节点,那么军队可以有两种选择,第一是不走到根节点回到上一个节点位置,第二是在剩余时间内走到与根节点相连的另一个节点。显然,如果时间充足但是某一个与根节点相连的节点上没有军队,我们肯定会让可以一个到达根节点的军队再到达该节点,否则当前节点的儿孙节点中所有的边境城市的疫情都无法控制。但出于时间问题,我们会选择一个剩余时间尽量少但又可以达到该节点的一个在根节点上的军队去到达该节点控制疫情。为什么剩余时间要尽量少呢?因为我们选用了剩余时间尽量少的军队,那么剩余时间较多的军队就可以去解决边权更大的未到达城市的疫情了。

宏观上来看,我们就是二分时间,判断当前时间是否可以控制所有疫情,然后进行下一步二分。时间复杂度为O(log2(Tot_Time)*m*log2(n))因为Tot_Time<=INT_MAX所以是可以的。

Tail:怎么样判断所有与根节点相连的节点的疫情是否控制了呢?(即是否控制了相连节点的所有儿孙节点)我们用一个数组Have[i]表示i的疫情是否控制,首先所有没有到达根节点的军队所到达的节点的疫情已经控制(显然),就可以标记其Have数组,然后从所有的叶子节点开始扩展,对于当前的节点i如果它的Have已经标记或者它有儿子节点并且所有儿子节点的Have已经标记,那么i的疫情已经控制,即标记Have[i]。这样dfs一轮下来时间复杂度为O(n),总时间复杂度为O(log2(Tot_Time)*n)也就是与上面的时间复杂度中的另外几项并列,所以不会超时。dfs可以得到所有与根节点相连的节点没有控制疫情的节点,然后根据它们离根节点的距离从小到大排序,然后选出可以到达根节点的军队中可以在剩余时间内到达该节点且剩余时间尽量小的军队去控制疫情。但要注意的是,到达根节点的军队一定要保证自己原来的节点的疫情已经控制,否则只能退回并控制自己的疫情,而不能跑到别的节点上去控制。

 

HInt:

  在让军队尽量接近根节点的时候要倍增,倍增一定要是从大到小。

  每一次倍增的时候一定要判断当前是否有父亲,否则会直接跳到空节点。

  对于一个到不了根节点的军队查找其所能到达的离根节点最近的节点的时候一定要重新倍增,因为之前的倍增受到的影响因素与现在受到的影响因素不同。

  注意二分的结束条件。


#include<cstdio>#include<cstring>#include<climits>#include<algorithm>using namespace std;const int MAXN = 50001;const int MAX = 16;int n,m;int Army[MAXN];struct EDGE{int Next,To,Dis;}Edge[MAXN<<1];int Size,Head[MAXN<<1];void Ins(int From,int To,int Dis){Edge[++Size].Next=Head[From];Head[From]=Size;Edge[Size].To=To;Edge[Size].Dis=Dis;} bool Vis[MAXN];int Father[MAXN][MAX+1],Dis[MAXN][MAX+1],Deep[MAXN];void Dfs(int u){for(int i=Head[u];i;i=Edge[i].Next){int v=Edge[i].To;if(Vis[v]) continue;Vis[v]=1;Father[v][0]=u;Dis[v][0]=Edge[i].Dis;Deep[v]=Deep[u]+1;Dfs(v);}}struct Point{int Dis,p;bool operator < (Point Tmp) const {return Dis<Tmp.Dis;}}Remain[MAXN],NotVis[MAXN];int Rsize,Nsize;bool Have[MAXN];void Updata(int u){bool p=1,q=0;for(int i=Head[u];i;i=Edge[i].Next){int v=Edge[i].To;if(Deep[v]<Deep[u]) continue;Updata(v);p=p&Have[v];q=1;}Have[u]=Have[u]|((p&q)&&u!=1);}bool Check(int Time){memset(Have,0,sizeof Have);Rsize=Nsize=0;for(int i=1;i<=m;i++){int u=Army[i],Now_Time=0,Son=Army[i];for(int j=MAX;j>=0;j--)if(Father[u][j]&&Now_Time+Dis[u][j]<=Time){Now_Time+=Dis[u][j];u=Father[u][j];}if(u!=1) Have[u]=1;else{for(int j=MAX;j>=0;j--) if(Father[Son][j]>1) Son=Father[Son][j];Remain[++Rsize].p=Son;Remain[Rsize].Dis=Time-Now_Time;}}sort(1+Remain,1+Rsize+Remain);Updata(1);for(int i=Head[1];i;i=Edge[i].Next)if(!Have[Edge[i].To]) NotVis[++Nsize].p=Edge[i].To,NotVis[Nsize].Dis=Edge[i].Dis;sort(1+NotVis,1+Nsize+NotVis);int Pos=1;for(int i=1;i<=Rsize;i++){if(!Have[Remain[i].p]) Have[Remain[i].p]=1;else if(Remain[i].Dis>=NotVis[Pos].Dis) Have[NotVis[Pos].p]=1;while(Have[NotVis[Pos].p]&&Pos<=Nsize) Pos++;}return Pos==Nsize+1;}void Divide(){int l=0,r=INT_MAX;while(l<r-1){int Mid=(l+r)>>1;if(Check(Mid)) r=Mid;else l=Mid;}if(Check(l)) printf("%d\n",l);else printf("%d\n",r);}template <typename Type> inline void Read(Type &in){char ch=getchar();in=0;for(;ch>'9'||ch<'0';ch=getchar());for(;ch>='0'&&ch<='9';ch=getchar()) in=in*10+ch-'0';}int main(){Read(n);int Tmp=0;for(int i=1,u,v,w;i<n;i++){Read(u);Read(v);Read(w);Ins(u,v,w);Ins(v,u,w);if(u==1||v==1) Tmp++;}Vis[1]=1;Dfs(1);for(int j=1;j<=MAX;j++)for(int i=1;i<=n;i++){Father[i][j]=Father[Father[i][j-1]][j-1];Dis[i][j]=Dis[i][j-1]+Dis[Father[i][j-1]][j-1];}Read(m);for(int i=1;i<=m;i++) Read(Army[i]);if(m<Tmp){printf("-1\n");return 0;}Divide();return 0;}


0 0
原创粉丝点击