当前位置: 代码迷 >> 综合 >> 洛谷 P1084 [NOIP2012 D2T3] 疫情控制
  详细解决方案

洛谷 P1084 [NOIP2012 D2T3] 疫情控制

热度:24   发布时间:2024-01-19 02:51:05.0

题目描述

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。

NOIP 2012 提高组 第二天 第三题

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

贪心+二分+搜索~

思路不难,可是代码非常难写,我居然写了140行……

因为边数组开小了,WA了……洛谷真的不修缮一下编译器bug么……

二分答案,类似于迭代加深搜索。

先预处理出每支军队最远可以到达的点,如果可以到达首都就记下到首都后还能走的长度,不能就记下还剩多长才能到首都,从大到小贪心地对应消去,能全部消去就代表可以在该时间内控制疫情。

(鉴于原来的代码没注释太难懂,我重加了一遍注释……这一次的应该要好很多了……)


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;int n,m,fi[100001],ne[100001],w[100001],v[100001];
int cnt,x,y,val,tot,arm[50001],ho[50001],dis[50001];
int ti[50001],gra[50001],na,nc,cal[50001];
bool b[50001];struct node{int x,v;
}arre[50001],coo[50001];  //多出来的,缺少的 bool cmp(node u,node v)
{return u.v>v.v;
}void addedge(int u,int vv,int z)
{w[++cnt]=vv;ne[cnt]=fi[u];fi[u]=cnt;v[cnt]=z;
}void dfs(int u)
{int maxx=-1,now1=0,now2=0;for(int i=fi[u];i;i=ne[i])if(w[i]!=ho[u])  //保证向下走 {dfs(w[i]);if(ti[w[i]]==-1) now2=1;  //该点需要军队弥补 if(ti[w[i]]>=v[i]) now1=1;  //可以走到该点 maxx=max(maxx,ti[w[i]]-v[i]);}if(u!=1 && ne[fi[u]])  //不是根节点且不是独苗(既有多个子节点的非根节点) {if(now1) ti[u]=max(ti[u],maxx);  //所有点中走得最长的一个记为该点剩下的长度 else if(now2) ti[u]=max(ti[u],-1);  //没有军队到达子节点,最低记为-1 else ti[u]=max(ti[u],0);  //每个子节点都有军队,但在该点的子节点与该点之间结束 }
}void maketi(int u)
{for(int i=1;i<=m;i++)if(dis[arm[i]]>=u) ti[arm[i]]=u;  //军队在时间限制内的已知距离,无法走出根节点则记为时间 dfs(1);  //从根节点开始遍历 
}void makeaa(int u)
{for(int i=1;i<=m;i++)if(dis[arm[i]]<u) arre[++na].x=gra[arm[i]],arre[na].v=u-dis[arm[i]];  //记录可用来弥补的距离及其现在位置(根节点直系子节点) sort(arre+1,arre+na+1,cmp);  //从大到小排序 for(int i=1;i<=na;i++){if(!cal[arre[i].x]) cal[arre[i].x]=i;  //每个子节点军队的最小弥补值的序号 else if(arre[cal[arre[i].x]].v>arre[i].v) cal[arre[i].x]=i;}
}void makecc()
{for(int i=fi[1];i;i=ne[i])  //所有需要弥补的根节点直系子节点 if(ti[w[i]]==-1) coo[++nc].x=w[i],coo[nc].v=v[i];sort(coo+1,coo+nc+1,cmp);  //距离从大到小排序 
}bool ans()
{if(na<nc) return 0;  //绝对无法弥补 memset(b,0,sizeof(b));int i=1,j=1;  //用来弥补的军队,须弥补的距离 for(;i<=nc;i++)if(!b[cal[coo[i].x]] && cal[coo[i].x]) b[cal[coo[i].x]]=1;  //有军队刚好可以弥补,无需经过根节点 else{while(b[j] && j<=na) j++;if(j>na || arre[j].v<coo[i].v) return 0;  //无法弥补 b[j]=1;j++;}return 1;
}bool pan(int u)
{memset(cal,0,sizeof(cal));na=nc=0;memset(ti,-1,sizeof(ti));maketi(u);makeaa(u);makecc();return ans();
}int erfen()  //二分答案 
{int l=-1,r=999999999;while(l+1<r){int mid=(l+r)/2;if(pan(mid)) r=mid;else l=mid;}return r;
}void getho(int u)
{for(int i=fi[u];i;i=ne[i])if(ho[u]!=w[i]){if(u==1) gra[w[i]]=w[i];  //记录该点跳转直系祖先(该祖先为1号节点的子节点) else gra[w[i]]=gra[u];ho[w[i]]=u;dis[w[i]]=dis[u]+v[i];  //该点父节点,距根节点距离 getho(w[i]);}
}int main()
{scanf("%d",&n);for(int i=1;i<n;i++){scanf("%d%d%d",&x,&y,&val);addedge(x,y,val);addedge(y,x,val);if(x==1 || y==1) tot++;}scanf("%d",&m);getho(1);if(m<tot){printf("-1\n");return 0;}for(int i=1;i<=m;i++) scanf("%d",&arm[i]);  //军队位置 sort(arm+1,arm+m+1);  //军队位置由近到远排序 printf("%d\n",erfen());return 0;
}