首页 > 技术文章 > 【BZOJ2525】[Poi2011]Dynamite 二分+树形DP

CQzhangyu 2017-11-19 09:36 原文

【BZOJ2525】[Poi2011]Dynamite

Description

Byteotian Cave的结构是一棵N个节点的树,其中某些点上面已经安置了炸.药,现在需要点燃M个点上的引线引爆所有的炸.药。
某个点上的引线被点燃后的1单位时间内,在树上和它相邻的点的引线会被点燃。如果一个有炸.药的点的引信被点燃,那么这个点上的炸.药会爆炸。
求引爆所有炸.药的最短时间。
输入:
第一行是两个整数N,M。(1<=m<=n<=300000)
接下来一行有N个整数Di,第I个数为1表示该点有炸.药。
接下来N-1行每行有两个数A,B,表示A和B之间有一条边。
输出:
最短时间。
样例解释: 
点燃3,5上的引线。

Sample Input

7 2
1 0 1 1 0 1 1
1 3
2 3
3 4
4 5
5 6
5 7

Sample Output

1

题解:一眼想到二分+贪心,但是细节还是极其多的~

首先二分时间limit,然后用f[i]表示i子树中,最少要点燃多少引线。但是i子树中的部分炸.药可能由i子树外的点引燃,所以设g[i]表示在f[i]最小的前提下,最少有多少层还没有被点燃;i子树中的引线也可能引燃子树外的炸.药,所以h[i]表示在f[i]最小的前提下,最多还能点燃子树外的多少层炸.药。显然g[x]和h[x]同时只能存在一个。

转移时细节挺多的,见代码吧~

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=300010;
int n,m,cnt,mid;
int to[maxn<<1],next[maxn<<1],head[maxn],d[maxn],f[maxn],g[maxn],h[maxn];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+(gc^'0'),gc=getchar();
	return ret*f;
}
inline void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
void dfs(int x,int fa)
{
	int i,y;
	if(d[x])	g[x]=0,h[x]=-1<<20;
	else	g[x]=h[x]=-1<<20;
	f[x]=0;
	for(i=head[x];i!=-1;i=next[i])	if(to[i]!=fa)
	{
		y=to[i],dfs(y,x),f[x]+=f[y],g[x]=max(g[x],g[y]+1),h[x]=max(h[x],h[y]-1);
	}
	if(h[x]>=g[x])	g[x]=-1<<20;
	else	if(g[x]<mid)	h[x]=-1<<20;
	else	f[x]++,g[x]=-1<<20,h[x]=mid;
}
int main()
{
	n=rd(),m=rd();
	int i,a,b,l=0,r=n;
	memset(head,-1,sizeof(head));
	for(i=1;i<=n;i++)	d[i]=rd();
	for(i=1;i<n;i++)	a=rd(),b=rd(),add(a,b),add(b,a);
	while(l<r)
	{
		mid=(l+r)>>1,dfs(1,0);
		if(g[1]>=0)	f[1]++;
		if(f[1]<=m)	r=mid;
		else	l=mid+1;
	}
	printf("%d",r);
	return 0;
}//5 0 0 0 0 0 0 1 2 2 3 3 4 4 5

 

推荐阅读