题目
数据范围
题解
在之前的一道题目中,可以得到一个结论:部分分的条件一定会对解题有帮助。
先看k=n的情况。显然是边数之和*2-直径。
拓展到一般情况,在进行状态转移的时候,要依照条件“边数之和*2-直径”进行转移。
那么有这么几点。
首先,转移的时候,一定保证被转移的状态已经最优,那么怎么在转移的时候保证边数之和
∗
2-直径最小呢?首先这个状态一定存储着当前边数之和
其次,直径的两个端点是否都在x(当前转移的点)上,这个要讨论。
综上所述,设
f0[x,i]
表示x的子树选了i个点,直径的一端在x上的所构成的树的边*2-直径的最小值。
f1[x,i]
表示x的子树选了i个点,直径的一端不在x上的所构成的树的边*2-直径的最小值。
那么如何让所有的情况补充不漏呢?操作x点时,儿子的子树一个一个讨论,讨论完合并。这就是树形背包的经典做法。
显然转移要讨论三种情况。
①直径在新的子树。②直径在已处理过的部分。
③
(u,v)
属于的直径能更新答案(对于更新
f1
来说)。
对于
f0
的更新,用含直径的部分的
f0
+不含直径的部分的
g
*2更新。
总结
①最重要的一点,操作x点时,儿子的子树一个一个讨论,讨论完合并。
②关于最短路径难以用一次转移式来表示的情况,那么每次保证转移最优即可。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 3010
#define Inf 1061109567
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
int to,next,val;
};note edge[N*2];
int tot,head[N],fa[N],siz[N],ans;
int i,K,n,u,v,z,temp,t1,t2;
int g[N][N],f0[N][N],f1[N][N];
void lb(int x,int y,int z){edge[++tot].to=y;edge[tot].next=head[x];edge[tot].val=z;head[x]=tot;}
void mi(int &x,int y){x=x<y?x:y;}
void dg(int x){
int i,j,k;
siz[x]=1;f0[x][1]=g[x][1]=0;
for(i=head[x];i;i=edge[i].next){
if(fa[x]==edge[i].to)continue;
fa[edge[i].to]=x;
dg(edge[i].to);
}
for(i=head[x];i;i=edge[i].next){
if(fa[x]==edge[i].to)continue;
t1=siz[x]<K?siz[x]:K;
t2=siz[edge[i].to]<K?siz[edge[i].to]:K;
fd(j,t1,1)
fd(k,t2,1)
if(j+k<=K){
temp=edge[i].val;
mi(g[x][j+k],g[x][j]+g[edge[i].to][k]+temp);
mi(f0[x][j+k],f0[x][j]+2*(g[edge[i].to][k]+temp));
mi(f0[x][j+k],f0[edge[i].to][k]+2*g[x][j]+temp);
mi(f1[x][j+k],f1[x][j]+2*(g[edge[i].to][k]+temp));
mi(f1[x][j+k],f1[edge[i].to][k]+2*(g[x][j]+temp));
mi(f1[x][j+k],f0[x][j]+f0[edge[i].to][k]+temp);
}
siz[x]+=siz[edge[i].to];
}
}
int main(){
scanf("%d%d",&n,&K);
fo(i,1,n-1){
scanf("%d%d%d",&u,&v,&z);
lb(u,v,z);lb(v,u,z);
}
memset(g,63,sizeof(g));
memset(f0,63,sizeof(f0));
memset(f1,63,sizeof(f1));
dg(1);
ans=Inf;
fo(i,1,n)mi(ans,f0[i][K]),mi(ans,f1[i][K]);
printf("%d",ans);
return 0;
}