[NOIP2018] 赛道修建
好题好题,太棒了这题!
直接想是十分困难的,你连 \(dp\) 状态都想不出合理的,因此考虑二分答案,转化成一个判定问题。下文 \(d\) 表示二分出的答案。
设 \(sum_i\) 表示 \(i\) 子树内的合法路径数,那他就一共分为两部分:
- 来自于 \(sum_{son}\),直接累加即可。
- 经过 \(i\) 的路径。
我们用一个 \(multiset\) 来存储每个儿子的 \(multiset\) 中还没有被配对的点中,距离子树根 最远的点。对于到 \(i\) 合法的路径,直接 \(sum_i++\) 即可;对于其他,从小到大进行配对,最后剩下的点中取最大值,再传递到子树根的父亲即可。
正确性显然,时间复杂度 \(O(n\log^2n)\)。
#include<bits/stdc++.h>
#define par pair<int,int>
#define int long long
using namespace std;
const int N=5e4+5;
int n,m,sum[N];
vector<par>g[N];
multiset<int>s[N];
int dfs(int x,int fa,int d){
s[x].clear(),sum[x]=0;
for(auto c:g[x]){
int y=c.first;
int z=c.second;
if(y==fa) continue;
int a=dfs(y,x,d)+z;
sum[x]+=sum[y];
if(a>=d) sum[x]++;
else s[x].insert(a);
}int mx=0;
while(s[x].size()){
int c=(*s[x].begin());
if(s[x].size()==1)
return max(mx,c);
auto y=s[x].lower_bound(d-c);
if(y==s[x].begin()) y++;
if(y==s[x].end()) mx=max(mx,c);
else s[x].erase(y),sum[x]++;
s[x].erase(s[x].begin());
}return mx;
}int check(int x){
dfs(1,0,x);
return (sum[1]>=m);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1,a,b,l;i<n;i++){
cin>>a>>b>>l;
g[a].push_back({b,l});
g[b].push_back({a,l});
}int l=0,r=5e8,ans;
while(l<=r){
int mid=(l+r)/2;
if(check(mid))
l=mid+1,ans=mid;
else r=mid-1;
}cout<<ans;
return 0;
}