cqyz oj | 健美操 | 树形DP | 二分猜答案

Description

  给出一棵树,N(2 <= N <= 100,000)个结点。每条边的长度为1。最多删掉树上的S(1 <= S <= N-1)条边,将树分割成S+1块,使得所有块的最长链的最大值最小。

Input

  第1行:2个整数,N和S  接下来N-1行,每行2个整数,表示一条边

Output

  一个整数,表示最长链的最小值

Sample Input 1

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

Sample Output 1

2

Hint

2 <= N <= 100,000


看到问题是最大值最小,二分猜答案没跑了。
问题是猜到答案过后怎么验证。

想到可以记录节点u到叶子的最远和次远距离,它们之和就是u的子树里的最长链
用dp[u]表示u到叶子节点的最远距离,新遍历完一个子树v后,如果dp[v]+dp[u]>猜的答案,就删除一条边,贪心思路选择删除dp[u]和dp[v]里最大的一个子树,保留较小的。如果不需要删除,就找dp[u]=max(dp[u],dp[v])

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define maxn 100005 
using namespace std;
int fir[maxn],ne[maxn*2],to[maxn*2],newp=0;
void add(int x,int y){
	ne[++newp]=fir[x];
	fir[x]=newp;
	to[newp]=y;
}
int dis[maxn];
int cnt,mid;
int n,s;
void dfs(int f,int u){
	if(cnt>s)return;
	dis[u]=0;
	for(int i=fir[u];i;i=ne[i]){
		int v=to[i];
		if(v==f)continue;
		
		dfs(u,v);
		if(dis[u]+dis[v]>mid)
			cnt++,dis[u]=min(dis[u],dis[v]);
		else dis[u]=max(dis[u],dis[v]);
	}
	dis[u]++;            //这里++后变为fa[u]在u这个子树里离叶子的最远距离
}
inline bool check(int mid){
	cnt=0;
	dfs(0,1);
	return cnt<=s;
}
int main(){
	scanf("%d%d",&n,&s);
	for(int i=1,a,b,c;i<n;i++){
		scanf("%d%d",&a,&b);
		add(a,b);add(b,a);
	}
	int l=0,r=n,ans;
	while(l<=r){
		mid=l+r>>1;
		if(check(mid))ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/de-compass/p/11253837.html