• 【NOIP2012】疫情控制(二分,倍增,贪心)


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

    题解

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

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

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

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

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

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

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

    #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
    ",l);
    	return 0;
    }
    
    
  • 相关阅读:
    Docker搭建redis集群
    PHP中的OPCode和OPCache
    Redis的三种集群模式
    MySQL事务的隔离级别
    Docker镜像分层技术
    为什么 MongoDB 选择B树,Mysql 选择B+树?
    MongoDB的使用
    cesium+vue挖坑展示
    Ceium+Vue踩坑记录
    渲染总结——记录
  • 原文地址:https://www.cnblogs.com/cjyyb/p/7581132.html
Copyright © 2020-2023  润新知