赛道修建 二分答案
赛道修建 二分答案
最小赛道长度的最大值,很明显的二分答案,即求出赛道长度均大于等于\(mid\)时最多赛道条数,判断是否大于\(m\)即可。
但是如何求出最多的条数?我们发现这是一颗树,可以用一种子树递归计算的思路,算出儿子节点的答案来算出父亲节点的答案,每次我们只管算出当前子树,再将一些情况上传到父亲,这是一个很妙的思路。
具体如何计算当前子树最大赛道数就咕咕咕了,主要是上面的思想
#include <cstdio>
#include <set>
#include <algorithm>
#define MAXN 50005
using namespace std;
int head[MAXN],vv[MAXN*2],ww[MAXN*2],nxt[MAXN*2],tot;
inline void add_edge(int u, int v, int w){
vv[++tot]=v;
ww[tot]=w;
nxt[tot]=head[u];
head[u]=tot;
}
multiset <int> s[MAXN];
multiset <int>::iterator iter;
int n,m,cnt;
int solve(int u, int fa, int k){
s[u].clear();
for(int i=head[u];i;i=nxt[i]){
int v=vv[i],w=ww[i];
if(v==fa) continue;
int val=w+solve(v, u, k);
if(val>=k) ++cnt;
else s[u].insert(val);
}
int res=0;
while(!s[u].empty()){
if(cnt>=m) return 0;
int cur=*s[u].begin();
if(s[u].size()==1) return max(res, cur);
iter=s[u].lower_bound(k-cur);
if(iter==s[u].begin()&&s[u].count(*iter)==1) iter++;
if(iter==s[u].end()){
res=max(res, cur);
s[u].erase(s[u].begin());
}else{
++cnt;
s[u].erase(iter);
s[u].erase(s[u].begin());
}
}
return res;
}
bool check(int k){
cnt=0;
solve(1,0,k);
if(cnt>=m) return 1;
return 0;
}
int main()
{
scanf("%d %d", &n, &m);
int l=1,r=0;
for(int i=1;i<=n-1;++i){
int u,v,w;
scanf("%d %d %d", &u, &v, &w);
add_edge(u,v,w);
add_edge(v,u,w);
r+=w;
}
int ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d", ans);
return 0;
}