洛谷 P1084 [NOIP2012 提高组] 疫情控制(二分,倍增,贪心)

Posted 尹昱钦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷 P1084 [NOIP2012 提高组] 疫情控制(二分,倍增,贪心)相关的知识,希望对你有一定的参考价值。

传送门


不得不说这题细节很恶心。

解题思路

二分最小时间x:
首先很显然的贪心是,每个节点的军队在时间x内一定要尽可能向上走,并且如果某个子树如果去支援别的子树,一定到的是子树的根节点(即根的儿子)。
所以我们可以用倍增判断在时间x内每个军队能到达的位置,把能到达根节点的并且还有剩余时间的拿出来,称之为“有能力的军队”,剩余时间我们称之为“能力值”,将其放到结构体A中。把其他“没有能力的军队”最终能到达的节点打上标记。
然后遍历一遍,求出所有“需要被帮助的根节点的儿子”以及支援此儿子所需要的“能力值”————即为根节点到这个儿子的边权,将其放到结构体B中。
接下来是解决本题的重点————贪心。
面对A中的“有能力的军队”和B中的“需要被支援的儿子”,应如何选择支援关系呢?
最先容易想到的是将其按照能力值排序后,能力值大军队去支援需要能力值大的儿子,但是我们没有考虑到如果某个有能力的军队原来所属的儿子本身就需要被支援,这个军队的一种选择可以是留在这个儿子这里,并且不管能力值大小一定能够支援此儿子。所以这种贪心是错误的。
正确的贪心是这样的:我们将A和B按照“能力值”和“支援需要的能力值”从大到小排序,然后遍历B,如果当前儿子位置有“有能力的军队”正在结构体A中准备支援别人,则让这个军队“自救”,否则就让A中目前能力值最大的军队支援这个儿子。
(某个儿子可能有多个“有能力的军队”,这时候很显然用能力值最小的军队自救)
可以感性理解一下,与其用能力值最大的军队支援某个儿子,然后用这个儿子上能力值比较小的军队支援别的儿子,不如这个儿子自救(消耗能力值小的军队),然后留下一个能力值大的军队去支援别的儿子。
细节真的多,调了我四节课www

AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=5e5+5;
int cnt,p[maxn],fa[maxn],dep[maxn],dp[maxn][25],num[maxn];
int n,m,a[maxn],is_need[maxn];
long long l,r,dis[maxn][25];
struct son{
	int id;
	long long rest;
}ans[maxn],need[maxn];
bool cmp(son x,son y){
	return x.rest>y.rest;
}
struct node{
	int v,next,w;
}e[maxn*2];
void insert(int u,int v,int w){
	cnt++;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=p[u];
	p[u]=cnt;
}
void dfs(int u,int f,long long d,int ddpp){
	fa[u]=f;
	dep[u]=d;
	dp[u][0]=f;
	dis[u][0]=d-dep[f];
	for(int i=1;(1<<i)<ddpp;i++){
		dp[u][i]=dp[dp[u][i-1]][i-1];
	}
	for(int i=1;(1<<i)<ddpp;i++){
		dis[u][i]=dis[u][i-1]+dis[dp[u][i-1]][i-1];
	}
	for(int i=p[u];i!=-1;i=e[i].next){
		if(e[i].v==f) continue;
		dfs(e[i].v,u,d+e[i].w,ddpp+1);
	}
}
bool ok(int u){
	if(num[u]) return 1;
	if(e[p[u]].next==-1) return 0;
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(v==fa[u]) continue;
		if(!ok(v)) return 0;
	}
	return 1;
}
bool check(long long x){
	memset(is_need,0,sizeof(is_need));
	memset(num,0,sizeof(num));
	int cnt_ans=0,cnt_need=0;
	for(int i=1;i<=m;i++){
		long long rm=x,u=a[i];
		for(int j=20;j>=0;j--){
			if(dp[u][j]>1&&rm>=dis[u][j]){
				rm-=dis[u][j];
				u=dp[u][j];
			}
		}
		if(fa[u]!=1||rm-dis[u][0]<=0){
			num[u]++;
		}else{
			ans[++cnt_ans].id=u;
			ans[cnt_ans].rest=rm-dis[u][0];
			is_need[u]++;
		}
	}
	for(int i=p[1];i!=-1;i=e[i].next){
		if(!ok(e[i].v)){
			need[++cnt_need].id=e[i].v;
			need[cnt_need].rest=e[i].w;
		}
	}
	sort(ans+1,ans+cnt_ans+1,cmp);
	sort(need+1,need+cnt_need+1,cmp);
	for(int i=1,j=1;i<=cnt_need;i++){
		if(is_need[need[i].id]>=1){
			is_need[need[i].id]--;
			continue;
		}
		while(j<=cnt_ans&&is_need[ans[j].id]==0) j++;
		if(j>cnt_ans||ans[j].rest<need[i].rest) return 0;
		is_need[ans[j].id]--;
		j++;
	}
	return 1;
}
int main(){
	memset(p,-1,sizeof(p));
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		insert(u,v,w);
		insert(v,u,w);
	}
	cin>>m;
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	dfs(1,-1,0,1);
	r=50000000000000;
	while(l<r){
		long long mid=(l+r)/2;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	if(l==50000000000000) cout<<-1<<endl;
	else cout<<l;
	return 0;
}

//NOIP2012提高组Day2 t3

以上是关于洛谷 P1084 [NOIP2012 提高组] 疫情控制(二分,倍增,贪心)的主要内容,如果未能解决你的问题,请参考以下文章

洛谷P1084 [NOIP2012提高组Day2T3]疫情控制

P1084 [NOIP2012 提高组] 疫情控制(倍增&贪心&二分)

[NOIP2012提高组]疫情控制

NOIP2012 洛谷P1084 疫情控制

[NOIP2012] 提高组 洛谷P1082 同余方程

[NOIP2012] 提高组 洛谷P1083 借教室