题意:给一棵n个点的树,每条边的有两个边权和,现将某m条边的长度设为对应的,剩下n-m-1条边的长度设为。问得到的树的直径最小为多少。。
题解:二分一个mid,check能否使这棵树的直径不大于mid。每次check时,由于k不大,可以用二维的树形dp来搞,定义表示p为根的子树内让i条边选a且保证子树内最长简单路径不超过mid时,子树内离p最远的点与p的距离最小值(有点绕,通过合理选择a来使这个距离尽量短)。最后如果,就说明当前mid可行。
从儿子v往当前点p转移的时候,一开始设为0,每转移一次就把这个加到里面,有点类似点分治时把两条链组合在一起的感觉。
P.S.这个二分有点妙,相当于二分了一个路径长度限制,在一定长度限制内去最短化最长路径。
细品......
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2e4+4;
int n,m;
ll f[21][N],t[21];
struct Edge {
int v,nxt,a,b;
}e[N<<1];
int head[N],etot;
int siz[N];
ll l,r;
template <class T> inline void smax(T &x,T y) {
x=x<y?y:x;
}
template <class T> inline void smin(T &x,T y) {
x=x>y?y:x;
}
inline int read() {
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
inline void init() {
etot=0;
memset(head,-1,sizeof(head));
memset(f,0x3f,sizeof(f));
}
inline void adde(int u,int v,int a,int b) {
e[++etot].nxt=head[u],e[etot].v=v,e[etot].a=a,e[etot].b=b,head[u]=etot;
}
inline void dfs(int p,int fa,ll lim) {
siz[p]=0;
f[0][p]=0;
for (int i=head[p];~i;i=e[i].nxt) {
int v=e[i].v;
if (v^fa) {
dfs(v,p,lim);
int pre=min(siz[p],m);
int cur=min(siz[v],m);
for (int j=0;j<=m;++j) t[j]=lim<<1;
for (int j=0;j<=pre;++j)
for (int k=0;k<=cur&&j+k<=m;++k) {
if (f[j][p]+f[k][v]+e[i].a<=lim)
smin(t[j+k+1],max(f[j][p],f[k][v]+e[i].a));
if (f[j][p]+f[k][v]+e[i].b<=lim)
smin(t[j+k],max(f[j][p],f[k][v]+e[i].b));
}
for (int j=0;j<=m;++j) f[j][p]=t[j];
siz[p]+=siz[v];
}
}
++siz[p];
}
inline bool ok(ll x) {
dfs(1,0,x);
return f[m][1]<=x;
}
inline ll find(ll le,ll ri) {
ll ret;
while (le<=ri) {
ll mid=le+ri>>1;
if (ok(mid)) ret=mid,ri=mid-1;
else le=mid+1;
}
return ret;
}
int main() {
int kase=read();
while (kase--) {
init();
n=read(),m=read();
l=r=0;
for (register int i=1;i<n;++i) {
int u=read(),v=read(),a=read(),b=read();
adde(u,v,a,b);
adde(v,u,a,b);
r+=max(a,b);
}
printf("%lld\n",find(l,r));
}
return 0;
}