bzoj2067: [Poi2004]SZN

传送门:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2067

思路:首先第一问就是最少多少笔画完这个图,ans=1+Σ(deg[i]-1)/2

第二问显然可以二分+判定。

先二分最长长度限制lim

怎么判定呢?

对于每个点,把它子树所有点向上需要的答案统计出来到a[]中,如果子树个数是偶数,则额外加一个a[i]=0

然后对a排序,二分删掉a中的一个元素,从大到小匹配判断是否合法,如果任何方案都不合法,则是不合法的直接退出

如果弄到最后都合法,这个答案就合法,不过要注意判断根的时候如果子树是偶数个不能额外加元素,也不能二分判断,而要直接判断合法性,否则两个点会错

--http://lbn187.is-programmer.com/posts/179515.html#


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int maxn=10010,maxm=20010;
using namespace std;
int n,pre[maxm],now[maxn],son[maxm],deg[maxn],tot,res=1,cnt,va[maxn],a[maxn];//a[i]儿子节点连上来的链的长度,va[i]i下面的链的最大值 
void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b,deg[b]++;}
bool check(int x,int lim){
	for (int l=1,r=cnt;l<=r;l++,r--){//贪心,第k大和第k小两两配对 
		if (l==x) l++;
		if (r==x) r--;
		if (a[l]+a[r]>lim) return 0;
	}
	return 1;
}

bool can(int x,int fa,int lim){
	//printf("%d %d %d\n",x,fa,lim);
	int ans=0;
	for (int y=now[x];y;y=pre[y]) 
		if (son[y]!=fa)
			if (!can(son[y],x,lim)) 
				return 0;
	cnt=0;
	for (int y=now[x];y;y=pre[y]) if (son[y]!=fa) a[++cnt]=va[son[y]]+1;
	if (x==1&&cnt%2==0){//根节点因为上面没有点来check,所以为偶数时要判断两两配对方案是否合法 
		va[x]=0,sort(a+1,a+1+cnt);
		for (int l=1,r=cnt;l<r;l++,r--) va[x]=max(va[x],a[l]+a[r]);
		return va[x]<=lim;
	}
	if (!(cnt&1)) a[++cnt]=0;sort(a+1,a+1+cnt);//如果为偶数,加一个为0的点 
	for (int l=1,r=cnt,mid;l<=r;){
		mid=(l+r)>>1;
		if (check(mid,lim)) ans=mid,r=mid-1;//二分不配对的节点 
		else l=mid+1;
	}
	if (!ans) va[x]=1e9;else va[x]=a[ans];
	return va[x]<=lim;
}

int main(){
	while (scanf("%d",&n)!=EOF){int ans=0;
		memset(now,0,sizeof(now)),memset(va,0,sizeof(va)),tot=0,res=1,memset(deg,0,sizeof(deg)),memset(a,0,sizeof(a));
		for (int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
		for (int i=1;i<=n;i++) res+=(deg[i]-1)>>1;
		for (int l=1,r=n-1,mid;l<=r;){
//			printf("fuckpp%d\n",(l+r)>>1);
			mid=((l+r)>>1);
			if (can(1,0,mid)) ans=mid,r=mid-1;
			else l=mid+1;
		}
		printf("%d %d\n",res,ans);
	}
	return 0;
}


你可能感兴趣的:(树形DP)