1007-In Search of Gold
题意
给你一颗(n)个结点的树,每条边有两种权值(a_i)和(b_i),你可以指定其中(k)条边的权值为(a_i),剩余(n-k-1)条边的权值为(b_i),使树的直径最小。
分析
二分树的直径(mid),然后树形(dp)来check,状态 (f[i][j]) 为结点 (i) 的子树中有 (j) 条边的权值为 (a_i) 的情况下 (i) 能到达的最远的距离的最小值,在一个结点合并两个儿子的时候判断一下合并完成的最长路径是不是小于等于 (mid) ,小于等于 (mid) 就转移,不然就不转移,最后若(f[root][k])不等于正无穷,就说明整棵子树里存在一种方案,(mid) 就可行,否则不行。注意枚举合并时可以用(j<=min(sz[x],k))来减枝,不然会 (TLE) 。
Code
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<sstream>
#include<cstdio>
#include<string>
#include<vector>
#include<bitset>
#include<queue>
#include<cmath>
#include<stack>
#include<set>
#include<map>
#define rep(i,x,n) for(int i=x;i<=n;++i)
#define per(i,n,x) for(int i=n;i>=x;--i)
#define sz(a) int(a.size())
#define rson mid+1,r,p<<1|1
#define pii pair<int,int>
#define lson l,mid,p<<1
#define ll long long
#define pb push_back
#define mp make_pair
#define se second
#define fi first
using namespace std;
const double eps=1e-8;
const int mod=1e9+7;
const int N=2e4+10;
const ll inf=1e18;
int T,n,k;
struct edg{
int x,a,b;
};
vector<edg>g[N];
ll f[N][22];
int sz[N];
ll mid;
ll ans[22];
void dfs(int u,int fa){
sz[u]=1;
f[u][0]=0;
for(edg it:g[u]){
int x=it.x,a=it.a,b=it.b;
if(x==fa) continue;
dfs(x,u);
int now=min(sz[u]+sz[x]+1,k);
int up=min(k,sz[x]),up1=min(k,sz[u]);
sz[u]+=sz[x];
rep(i,0,now) ans[i]=inf;
for(int i=0;i<=up1;i++){
for(int j=0;j<=up&&i+j<=k;j++){
if(f[u][i]+f[x][j]+b<=mid){
ans[i+j]=min(ans[i+j],max(f[u][i],f[x][j]+b));
}
if(i+j+1<=k&&f[u][i]+f[x][j]+a<=mid){
ans[i+j+1]=min(ans[i+j+1],max(f[u][i],f[x][j]+a));
}
}
}
rep(i,0,now) f[u][i]=ans[i];
}
}
int main(){
//ios::sync_with_stdio(false);
//freopen("in","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
ll l=1,r=0;
rep(i,1,n-1){
int u,v,a,b;
scanf("%d%d%d%d",&u,&v,&a,&b);
g[u].pb(edg{v,a,b});
g[v].pb(edg{u,a,b});
r+=max(a,b);
}
while(l<=r){
mid=l+r>>1;
rep(i,1,n) rep(j,0,k) f[i][j]=inf;
dfs(1,0);
if(f[1][k]!=inf) r=mid-1;
else l=mid+1;
}
printf("%lld
",l);
rep(i,1,n) g[i].clear();
}
return 0;
}