传送门
参考博客
如何检验答案?考虑贪心。令当前二分的答案为
k
k
k。
我们考虑一个节点
u
u
u,如果从它子树走向它,可以凑出长度大等于
k
k
k的边,我们就给答案加上一。否则我们把这条链存起来。
遍历完它的儿子以后,我们存了一些长度不满
k
k
k的边,考虑用它们凑出长度大等于
k
k
k的边。凑得出来,就删掉这两条边,给答案加上一。如果剩的有边,我们就把最大的一条给传上去。如果不剩边,就把
0
0
0传上去。
实际上就是贪心地凑边。在感性认知上,这样好像是对的。。要先求直径确定二分上界,防止被菊花图卡掉。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e4+10;
int Head[maxn],Next[maxn<<1],V[maxn<<1],W[maxn<<1],cnt=0;
int n,m,u,v,w,len,ans=0;
multiset<int> s[maxn];
multiset<int>::iterator it;
inline void add(int u,int v,int w){Next[++cnt]=Head[u],V[cnt]=v,W[cnt]=w,Head[u]=cnt;}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
//求直径
int dfs1(int u,int fa){
int sum1=0,sum2=0;
for(int i=Head[u];i;i=Next[i]) if(V[i]!=fa){
sum2=max(sum2,dfs1(V[i],u)+W[i]);
if(sum1<sum2) swap(sum1,sum2);
}len=max(len,sum1+sum2);
return sum1;
}
inline int dfs(int u,int fa,int k){
s[u].clear();
for(int i=Head[u];i;i=Next[i]) if(V[i]!=fa){
int val=dfs(V[i],u,k)+W[i];
if(val>=k) ++ans;
else s[u].insert(val);
}
int mx=0;
while(!s[u].empty()){
if(s[u].size()==1) return max(mx,*s[u].begin());
it=s[u].lower_bound(k-*s[u].begin());
if(it==s[u].begin()&&s[u].count(*it)==1) it++;
if(it==s[u].end()){
mx=max(mx,*s[u].begin());
s[u].erase(s[u].find(*s[u].begin()));
}
else{
++ans;
s[u].erase(s[u].find(*it));
s[u].erase(s[u].find(*s[u].begin()));
}
}return mx;
}
inline bool check(int k){
ans=0,dfs(1,0,k);
return ans>=m;
}
int main(){
n=read(),m=read();
for(int i=1;i<n;++i)
u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
dfs1(1,0);
int l=1,r=len;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}printf("%d\n",l);
}