疫情控制(二分+贪心+倍增)

yi情控制

来源:
NOIP 2012 提高组 第二天 第三题
题目描述:
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,也是树中的根节点。
H 国的首都爆发了一种wei hai性极高的chuan ran病。当局为了控制yi情,不让yi情扩散到边境
城市(叶子节点所表示的城市),决定动用jun队在一些城市建立检查点,使得从shou都到bian境城市的每一条路径上都至少有一个检查点,bian境城市也可以建立检查点。但特别要注意的是,shou都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有jun队,且一个城市可以驻扎多个jun队。一支jun队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在
一个城市建立检查点。一支jun队经过一条道路从一个城市移动到另一个城市所需要的时间等
于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制yi情。注意:不同的jun队可以同时移动。
输入输出格式
输入格式:
第一行一个整数 n,表示城市个数。
接下来的 n-1 行,每行 3 个整数,u、v、w,每两个整数之间用一个空格隔开,表示从
城市 u 到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。
接下来一行一个整数 m,表示jun队个数。
接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个jun队所驻扎的城市的编号。
输出格式:
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
输入输出样例
输入样例#1:
4
1 2 1
1 3 2
3 4 3
2
2 2
输出样例#1:
3
说明
【输入输出样例说明】
第一支jun队在 2 号点设立检查点,第二支jun队从 2 号点移动到 3 号点设立检查点,所需时间为 3 个小时。
【数据范围】
保证jun队不会驻扎在首都。
对于 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。

思路:
二分答案+倍增检验+贪心思路
首先明白目的是什么:求把首都(1节点)围住的最少时间,注意:不一定非要到根节点的儿子节点才可以,把这一条路封住就可以了。
所以二分一个时间后,是这个点尽量往上走(尽量接近首都),这里会出现两种情况:1、二分的时间不够到达首都,在中途停了下来,停下来的点设为访问过。2、二分的时间足够到达首都,开个结构体b记录一下。
对于第一种情况,因为这个点没有到首都,所以进行标记上传。至于什么作用,接着往下看。
对于第二种情况,这个点到达了首都,它就有两种选择,一是回到它来的路径上那个最靠近首都的点,二是跨过根节点去覆盖其他的点。
开一个c结构体,仅仅扫描首都节点的儿子!记录没有jun队的儿子,c[i].from表示首都的儿子,c[i].w表示首都到这个儿子的时间。
说到这里,就很容易理解标记上传了,因为第一种情况中说到了,这个点没有到达首都,它可能是首都的儿子,也可能是首都的孙子,更可能是距离首都很远的一个点,但是它算在的这条路径已经是被这个点覆盖了(疫情不会从这里扩散出去!),然而结构体c仅仅扫描了首都的儿子节点,所以在这条路径上的首都的儿子可能没有覆盖,所以写一个函数color把这个点的情况传给它所在路径上的首都的儿子,也就是所谓的标记上传。
现在,就可以扫描首都儿子,在结构体c中进行记录了。
记录完成之后,继续用到贪心思路,b、c结构体排序。
具体情况在代码中会有详解!
最后输出答案
完成

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=50010;
int l,r,mid,ans=-1;
int n,m,tot,head[maxn];
int f[maxn][20],dis[maxn][20],deep[maxn];
int a[maxn],vis[maxn];
struct edge
{
	int to;
	int w;
	int next;
}e[maxn<<1];
struct node
{
	int w;
	int from;
}b[maxn<<1],c[maxn<<1];
void add_edge(int u,int v,int w)
{
	tot++;
	e[tot].to=v;
	e[tot].w=w;
	e[tot].next=head[u];
	head[u]=tot;
}
void build(int u)
{
	for(int i=head[u];i;i=e[i].next)
	if(!f[e[i].to][0])
	{
		f[e[i].to][0]=u;
		dis[e[i].to][0]=e[i].w;
		deep[e[i].to]=deep[u]+1;
		build(e[i].to);
	}
}
void init()
{
	for(int j=1;j<=19;j++)
	  for(int i=1;i<=n;i++)
	  {
	  	f[i][j]=f[f[i][j-1]][j-1];
	  	dis[i][j]=dis[i][j-1]+dis[f[i][j-1]][j-1];
	  }
}
void color(int x)//标记上传,因为下面结构体c是扫描的 
{
	int p=1,q=0;
	for(int i=head[x];i;i=e[i].next)
	if(e[i].to!=f[x][0])
	{
		color(e[i].to);
		p=p&vis[e[i].to];
		q=1;
	}
	if(p&&q&&x!=1)
	vis[x]=1;
}
bool cmp(node x,node y)
{
	return x.w<y.w;
}
int can(int x)
{
	memset(vis,0,sizeof(vis));
	int cnt=0;//多少点到达了首都
	int top=0;//有多少首都的儿子没有jun队
	for(int i=1;i<=m;i++)//枚举每一个jun队可以上跳到哪个祖先
	{
		int y=a[i],z=0;
		for(int j=19;j>=0;j--)//使劲往上跳
		if(f[y][j]&&dis[y][j]+z<=x)
		{
			z+=dis[y][j];
			y=f[y][j];
		}
		if(y!=1) vis[y]=1;//到不了首都
		else//到了首都,用b记录
		{
			b[++cnt].w=x-z;剩余的时间
			y=a[i];
			for(int j=19;j>=0;j--)
			if(f[y][j]>1)
			y=f[y][j];
			b[cnt].from=y;//来的路径上离首都最近的那个点(首都儿子)
		}
	}
	color(1);//标记上传
	for(int i=head[1];i;i=e[i].next)//c记录哪个首都儿子没有jun队
	if(!vis[e[i].to])
	{
		c[++top].from=e[i].to;//首都儿子的编号
		c[top].w=e[i].w;//到达首都的时间
	}
	sort(b+1,b+cnt+1,cmp);//把到达首都点的剩余时间从大到小排序
	sort(c+1,c+top+1,cmp);//把没有jun队的首都儿子从小到大排序
	int j=1;c[top+1].w=0x3f3f3f3f;
	for(int i=1;i<=cnt;i++)
	{
		if(!vis[b[i].from]) vis[b[i].from]=1;//回到原来的点
		else if(b[i].w>=c[j].w) vis[c[j].from]=1;//贪心覆盖
		while(vis[c[j].from]) j++;
	}
	if(j>top)
	return 1;
	return 0;
}
int main()
{
	int x,y,z;
	cin>>n;
	for(int i=1;i<=n-1;i++)//加边
	{
		cin>>x>>y>>z;r+=z;
		add_edge(x,y,z);
		add_edge(y,x,z);
	}
	cin>>m;
	for(int i=1;i<=m;i++)
	cin>>a[i];
	deep[1]=1;
	f[1][0]=1;
	build(1);//lca见树
	init();//lca预处理
	while(l<=r)//二分答案
	{
		mid=(r+l)>>1;
		if(can(mid))
		{
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	cout<<ans;
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值