[题解]NOIP2018_赛道修建(二分/树形dp/set/贪心

部分分:m=1 直径,菊花图 对边权排序,每次取最大最小合并,链 dp覆盖啥的

二叉树的话,二分答案,每个儿子要么和x连在一起往上传,要么和另外一个儿子合并(长度和大于mid),(统计以点x为lca的),x取大的儿子的f值,因为

正解把菊花图和二叉树合在一起,对每个点的儿子不断取两个边权加起来刚好大于mid的,然后剩下的取一个最大的传给父亲,用set实现

说起来容易做起来难....

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=50009;
int n,m,ans;
struct node{
    int v,nxt,w;
}e[maxn<<1];
int head[maxn],cnt;
inline void add(int u,int v,int w){
    e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt;
}
multiset<int>s[maxn];
ll dfs(int x,int fa,int k){
    s[x].clear();
    int val;
    for(int i=head[x];i;i=e[i].nxt){
        int y=e[i].v;if(y==fa)continue;
        val=dfs(y,x,k)+e[i].w;
        if(val>=k)ans++;
        else s[x].insert(val);
    }
    int mx=0;
    while(!s[x].empty()){
        if(s[x].size()==1)return max(mx,*s[x].begin());
        multiset<int>::iterator it=s[x].lower_bound(k-*s[x].begin());//找到一个可以和当前加起来>=lmt的最小元素 
        if(it==s[x].begin()&&s[x].count(*it)==1)it++;
        if(it==s[x].end()){//没找到取max上传 
            mx=max(mx,*s[x].begin());
            s[x].erase(s[x].find(*s[x].begin()));
        }
        else{//合并,删掉元素 
            ans++;
            s[x].erase(s[x].find(*it));
            s[x].erase(s[x].find(*s[x].begin()));
        }
    }
    return mx;
}
bool check(ll md){
    ans=0;
    dfs(1,0,md);
    if(ans>=m)return 1;
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);ll r=1;
    for(int i=1,u,v,w;i<n;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);r+=w;
    }
    ll l=0;r/=(ll)m;r+=1;
    while(l<r){
        ll mid=l+r>>1;
        if(check(mid))l=mid+1;
        else r=mid;
    }
    printf("%lld",r-1);
}

 

posted @ 2019-10-31 09:50  羊肉汤泡煎饼  阅读(230)  评论(0编辑  收藏  举报