1. 程式人生 > 其它 >洛谷 P1084 [NOIP2012 提高組] 疫情控制(二分,倍增,貪心)

洛谷 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