题目链接
二分答案。
然后用dp来check,
dp x y表示x节点,子树中用了y个来自a的边的离x节点最远的点的距离的最小值。
转移的时候,只合并直径小于mid的情况。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
int t,n,ka;
const LL inf=1e18;
struct uzi{
int x,a,b;
};
vector<uzi>v[N];
LL dp[N][26];
int sz[N];
LL tmp[26],mid;
void dfs(int x,int y){
sz[x]=1;int st=0;
for(auto k:v[x]){
if(k.x!=y){
dfs(k.x,x);
int nx=min(ka,sz[k.x]);//子树sz
int re=min(sz[x],ka);//自己当前已经合并过的
int can=min(ka,nx+re+1);//可以用到最多的
sz[x]+=sz[k.x];
for(int i=0;i<=can;i++)tmp[i]=inf;//转移数组
if(!st){
for(int i=0;i<=nx;i++){
if(i+1<=ka){
tmp[i+1]=min(tmp[i+1],dp[k.x][i]+k.a);
}
tmp[i]=min(tmp[i],dp[k.x][i]+k.b);
}
for(int i=0;i<=nx+1;i++){
dp[x][i]=tmp[i];
}
st=1;
continue;
}
for(int i=0;i<=re;i++){
for(int j=0;j<=nx&&j+i<=can;j++){
//如果能用a数组的边
if(dp[x][i]+dp[k.x][j]+k.a<=mid){//转移合法
tmp[i+j+1]=min(tmp[i+j+1],max(dp[x][i],dp[k.x][j]+k.a));
}
if(dp[x][i]+dp[k.x][j]+k.b<=mid){
tmp[i+j]=min(tmp[i+j],max(dp[x][i],dp[k.x][j]+k.b));
}
}
}
for(int i=0;i<=can;i++){
dp[x][i]=tmp[i];
}
sz[x]=can;
}
}
if(v[x].size()==1 && y) dp[x][0]=0;
}
int check(LL x){
dfs(1,0);
return dp[1][ka]<=x;
}
int main() {
ios::sync_with_stdio(false);
for(cin>>t;t;t--){
cin>>n>>ka;
for(int i=1;i<=n;i++){
for(int j=0;j<=ka;j++)dp[i][j]=inf;
v[i].clear();
}
LL l=1,r=0,ans;
for(int i=1;i<n;i++){
int s,t,a,b;
cin>>s>>t>>a>>b;
v[s].pb({t,a,b});
v[t].pb({s,a,b});
r+=max(a,b);
}
while(l<=r){
mid=l+r>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<'\n';
}
return 0;
}