赛道修建 二分答案

赛道修建 二分答案

最小赛道长度的最大值,很明显的二分答案,即求出赛道长度均大于等于\(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;
}

posted @ 2019-08-16 22:50  Santiego  阅读(138)  评论(0编辑  收藏  举报