【NOIP2018提高组D1T3】赛道修建 题解
很妙的方法。
题目
在此。
解题思路
关键词:二分,贪心,动态规划
看到要求最小值最大,显然是个二分。
二分一下答案,设为
l
i
m
lim
lim,即我们要在这棵树中选大于等于
m
m
m条链,长度大于等于
l
i
m
lim
lim。
设
f
x
,
r
e
s
t
x
f_x,rest_x
fx,restx分别表示以
x
x
x为根的子树最多可以弄出几条合法的链,以及剩下的地方最长的不合法链的长度。
然后转移显然,就是
f
x
=
s
+
∑
i
∈
s
o
n
x
f
i
f_x=s+\sum_{i\in son_x}{f_i}
fx=s+∑i∈sonxfi。
如何求
s
s
s呢?
现在有一些链是不合法的,我们将它加上
x
x
x到它的长度,如果合法了,
s
s
s就加一,否则记录下来,从大到小排序。
贪心配对,用最大的与最小的配对,如果不合法,就配对一个更大的,用双指针实现。
我们如何求
r
e
s
t
rest
rest呢?考虑如果把某一条边删掉,而答案不会改变,就去这些边最大的一条边即可。
code
可以打一个函数,这样程序较简短。
#include<bits/stdc++.h>
#define N 50005
using namespace std;
int n,m,la[N],we[N<<1],to[N<<1],ne[N<<1],cnt;
int f[N],rest[N],lim,a[N],tot;
void add(int x,int y,int z){
to[++cnt]=y;
we[cnt]=z;
ne[cnt]=la[x];
la[x]=cnt;
}
bool cmp(int x,int y){
return x>y;
}
int match(int u){
int i=1,j=tot,s=0;
while(i<j){
if(i==u){
i++;
continue;
}
if(j==u){
j--;
continue;
}
if(a[i]+a[j]>=lim) s++,i++,j--;
else j--;
}
return s;
}
void dfs(int x,int fa){
for(int i=la[x];i;i=ne[i])
if(to[i]!=fa){
dfs(to[i],x);
f[x]+=f[to[i]];
}
tot=0;
for(int i=la[x];i;i=ne[i]){
int son=to[i];
if(son==fa) continue;
if(rest[son]+we[i]>=lim) f[x]++;
else a[++tot]=rest[son]+we[i];
}
sort(a+1,a+tot+1,cmp);
int v=match(0);
int l=1,r=tot,s=0;
while(l<=r){
int mid=(l+r)>>1;
if(match(mid)==v) r=mid-1,s=a[mid];
else l=mid+1;
}
f[x]+=v;
rest[x]=s;
}
bool check(int x){
memset(rest,0,sizeof(rest));
memset(f,0,sizeof(f));
lim=x;
dfs(1,0);
if(f[1]>=m) return true;
else return false;
}
int main(){
freopen("track.in","r",stdin);
freopen("track.out","w",stdout);
scanf("%d%d",&n,&m);
int l,r=0,ans=0;
for(int x,y,z,i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
r+=z;
}
l=1;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",ans);
}