题目大意:给出一张n个点的无根树,边权均为1。现可以选择断m条边,获得 m+1棵树,问断边后所有树的直径的最大值最小是多少。
不难想到二分答案,如何判断是否合法呢
将无根树转成有根树后考虑以x为根的子树内的情况。
子树内若有超过答案的就必须通过断一条边使其小于答案。按链长排序依次考虑最长的两条边,若超过答案则断掉最长的。这样对于每一个子树都不超过限制整个也就不超过限制,每次断掉的都是最长的能保证断掉的边尽量少。
最后判断断掉的边数是否大于m即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100005
using namespace std;
struct Edge {
int from,to,nxt;
Edge() {}
Edge(int _from,int _to,int _nxt):
from(_from),to(_to),nxt(_nxt) {}
}e[N*2];
int n,m,cnt,lim,tot=-1,fir[N],f[N],s[N];
void Add_Edge(int u,int v) {
e[++tot]=Edge(u,v,fir[u]), fir[u]=tot;
e[++tot]=Edge(v,u,fir[v]), fir[v]=tot;
return ;
}
inline bool cmp(const int& lhs,const int& rhs) { return lhs>rhs; }
void dfs(int x,int pa) {
f[x]=0;
for(int i=fir[x];~i;i=e[i].nxt) {
if(e[i].to==pa) continue;
dfs(e[i].to,x);
}
int top=0;
for(int i=fir[x];~i;i=e[i].nxt)
if(e[i].to!=pa)
s[++top]=f[e[i].to]+1;
if(!top) return ;
sort(s+1,s+top+1,cmp);
for(int i=1;i<top;++i)
if(s[i]+s[i+1]>lim)
++cnt, s[i]=0;
if(s[top]>lim) ++cnt, s[top]=0;
sort(s+1,s+top+1,cmp);
f[x]=s[1];
return ;
}
bool check(int x) {
lim=x, cnt=0;
dfs(1,0);
return cnt<=m;
}
int main() {
memset(fir,-1,sizeof fir);
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<n;++i) scanf("%d%d",&x,&y), Add_Edge(x,y);
int l=0,r=n,mid,ans;
while(l<=r) {
mid=l+r>>1;
if(check(mid)) ans=mid, r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
return 0;
}