疫情控制

2 篇文章 0 订阅
1 篇文章 0 订阅

题目链接

https://www.luogu.org/problemnew/show/1084

需要先吐槽一下noip竟然还有这种题,,,

二分答案+倍增+贪心

首先先要明白如何将疫情控制住,就是把首都都围起来,然后求出最少时间

不一定非要到根节点首都的儿子节点,只需要把这条路封死就可以

二分时间,是目前的这个点尽量靠近根节点(首都),这里需要讨论一下:二分的时间不够到达首都,停下来此时的点被访问过

二分时间足够到达首都,此时定义一个结构体b记录,对于第一种情况,因为这个点没有到首都,所以进行标记上传

对于第二种情况,这个点到达了首都,它就有两种选择,一是回到它来的路径上那个最靠近首都的点,二是跨过根节点去覆盖其他的点

定义一个c结构体,仅仅遍历首都节点的儿子,记录没有军队的儿子

c[i].from表示首都的儿子,c[i].w表示首都到这个儿子的时间

那么这里我们为什么要标记上传呢?

在第一种情况中,如果一个点没有到达首都,它可能首都的儿子,也可能是首都的孙子,更可能重孙子,,(距离更远的点)

但是它已经被算入这条路径中被这个点覆盖了,疫情不会从这里扩散

而c结构体仅仅遍历首都节点的儿子,所以这条路径上的儿子可能没有被覆盖,所以要写一个update函数把这个点的情况传给它所在路径上首都的儿子

然后遍历记录首都的儿子,利用贪心的思想,对b,c进行排序

军队可以同时走,所以在一定时间内,军队越往根跳覆盖的就越多

代码里有详解

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#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 re{
	int to,w,next;
}e[maxn*2];
struct node{
	int w;
	int from;
}b[maxn*2],c[maxn*2];
void add(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 lca_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 update(int x)
{
	int p=1,q=0;
	for(int i=head[x];i;i=e[i].next)
	if(e[i].to!=f[x][0])
	{
		update(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 ok(int x)
{
	memset(vis,0,sizeof(vis));
	int cnt=0;//多少军队到达首都
	int top=0;//有多少首都的儿子没有军队
	for(int i=1;i<=m;i++)
	{
		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[++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;//首都儿子(离首都最近的点) 
		} 
	} 
	update(1);
	for(int i=head[1];i;i=e[i].next)//c记录哪个首都儿子没有军队 
	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);//把没有军队的首都儿子从小到大排序
	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(x,y,z);
		add(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();
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(ok(mid))
		{
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	cout<<ans<<endl;
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值