首页 > 技术文章 > 【NOIP2012】疫情控制(二分,倍增,贪心)

cjyyb 2017-09-23 14:43 原文

洛谷上的题目链接,题目不在赘述

题解

既然要时间最短,首先考虑二分。
因此,考虑二分时间,问题转换为如何检查能否到达。
如果一支军队一直向上走,能够到达根节点,那么他可以通过根节点到达其他的节点,因此这样的节点要单独拿出来匹配。
如果不能到达根节点,很显然,停在深度越低的位置,能够控制的子树越大,因此停留在深度最低的位置。
至于如何向上移动,显然不能够模拟,因此使用倍增计算向上移动。

因为有一些节点停在了中间,因此需要检查是否能够通过某些中间节点控制某棵子树。所以这里需要搜索一遍,检查哪些节点已经被那些停留的节点所控制。

接下来,剩下了一些东西:为被控制的根节点的儿子,和若干可以到达根节点的军队。

先在的问题就转换为了军队-节点的匹配。

很显然的一个贪心,到达根节点之后,如果一个军队剩余能够移动的时间越短,他就更加适合控制到根节点路径较短的节点相匹配。

但是这样还是有点小问题的。
因此,将贪心分点考虑如下:
①分别将子节点按照到根的路径长度,剩余军队按照剩余时间排序。
②显然当前军队要找到一个能够和自身匹配的军队为止(能够匹配的条件是:要么从这个子树上来的,要么剩余时间大于路径)
③在匹配过程中,如果一个节点因为剩余时间不够,无法用来匹配,则强行退回到上来的那棵子树
④如果一个军队匹配上了某一个节点,但是自己军队所上来的那个节点还没有被匹配,则优先退回去匹配。

这题真的很好,贪心一定要自己慢慢细想。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
using namespace std;
#define MAX 52000
#define INF 1000000000
struct Line
{
	int v,next,w;
}e[MAX*2];
int cnt=1,h[MAX];
long long f[MAX][20];
int ned[MAX];
int dis[MAX],N,st[MAX],fr[MAX];
int d[MAX][20],M;
bool vis[MAX];
inline void Add(int u,int v,int w)
{
	e[cnt]=(Line){v,h[u],w};
	h[u]=cnt++;
}
inline int read()
{
	int x=0,t=1;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=-1,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return x*t;
}
void DFS(int u,int ff)
{
	for(int i=h[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==ff)continue;
		f[v][0]=u;
		d[v][0]=e[i].w;
		dis[v]=dis[u]+e[i].w;
		DFS(v,u);
	}
}
inline void Pre()
{
	DFS(1,0);
	for(int j=1;j<=16;++j)
		for(int i=1;i<=N;++i)
		{
			f[i][j]=f[f[i][j-1]][j-1];
			d[i][j]=d[i][j-1]+d[f[i][j-1]][j-1];
		}
}
inline bool cmp(int a,int b)
{
	return dis[a]<dis[b];
}
inline int getc(int x)//计算当前节点在根节点的哪一个儿子节点的子树上
{
	for(int i=16;i>=0;--i)
		if(f[x][i]>1)x=f[x][i];
	return x;
}
inline int Stop(int x,int t)//在t时间内能够到达的最大祖先位置
{
	for(int i=16;i>=0;--i)
		if(f[x][i]&&d[x][i]<=t)
		{
			t-=d[x][i];
			x=f[x][i];
		}
	return x;
}
void DFS2(int x,int f)//检查控制
{
	if(vis[x])return;
	bool fl=true,dd=false;
	for(int i=h[x];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==f)continue;
		dd=true;//检查是不是叶子节点
		DFS2(v,x);
		if(!vis[v])
			fl=false;
	}
	vis[x]=fl&dd;
}
struct alive
{
	int st,tt;
}ar[MAX];
bool operator <(alive a,alive b)
{
	return a.tt<b.tt;
}
bool check(int t)
{
	int cnt=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=M;++i)
	{
		int up=Stop(st[i],t);
		if(up!=1)//不能够到达根节点
			vis[Stop(st[i],t)]=true;//停在能够到达的最小深度
		else
			ar[++cnt]=(alive){fr[i],t-dis[st[i]]};
	}
	DFS2(1,0);
	int cnt1=0,tot=0;
	for(int i=h[1];i;i=e[i].next)//统计没有被控制的子树
		if(!vis[e[i].v])
			ned[++cnt1]=e[i].v;
	sort(&ar[1],&ar[cnt+1]);//按照剩余时间排序
	sort(&ned[1],&ned[cnt1+1],cmp);//按照距离排序
	int now=1;tot=cnt;
	for(int i=1;i<=cnt1;++i)//未被控制的子树一一匹配
	{
		if(vis[ned[i]])continue;
		if(now==tot+1)return false;//当前的军队已经用完
		while(233)
		{
			if(ar[now].tt<dis[ned[i]]&&now!=tot)
			{
				vis[ar[now].st]=true;//不能够解决别的地方,强行退回去
				if(ned[i]==ar[now].st)break;
				++now;//计算可以使用的时间剩余最少的军队
			}
			else
			{
				if(now==tot)break;
				if(ar[now].st!=ned[i]&&(!vis[ar[now].st]))//虽然可以解决当前问题,但是自己所在子树未解决
				{
					vis[ar[now].st]=true;//强行退回去
					++now;
				}
				else
					break;
			}
		}
		if(now==tot)
			if(ar[now].tt<dis[ned[i]]&&ar[now].st!=ned[i])return false;
		vis[ned[i]]=true;
		now++;
	}
	return true;
}
int main()
{
	N=read();
	for(int i=1;i<N;++i)
	{
		int u=read(),v=read(),w=read();
		Add(u,v,w);Add(v,u,w);
	}
	Pre();
	M=read();
	for(int i=1;i<=M;++i)st[i]=read();
	for(int i=1;i<=M;++i)fr[i]=getc(st[i]);//计算每个儿子是从哪里来的
	int l=0,r=INF;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
	return 0;
}

推荐阅读