[题解]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); }