题目链接:传送门
首先发现所有选出的点一定是一个联通块。
考虑怎样排列这个联通块中的点能使答案最小:
从这个联通块的直径一端沿着直径走,每次访问一个新的节点就把它除直径上连接的点外所有连接的点都访问一遍,再沿着直径继续走。珂以证明这样走答案是最小的qwq。
不难发现除了直径外的所有边都要走两次,直径上的边只用走一次。
令
d
p
[
i
]
[
j
]
[
0
/
1
/
2
]
dp[i][j][0/1/2]
dp[i][j][0/1/2]表示
i
i
i的子树内选出了
j
j
j个点,其中包含0/1/2个
最
终
联
通
块
中
的
\color{red}最终联通块中的
最终联通块中的直径的端点的最小答案。
那么在树上跑背包,大莉分类讨论即可qwq(具体见代码,代码中有详细注释)。
毒瘤代码
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
inline char GetChar() {
char ch=getchar();
while(ch!='Q' && ch!='B') ch=getchar();
return ch;
}
const int Size=3005;
namespace I_Love {
int n,k,cnt,head[Size];
struct Edge {
int v,t,next;
} w[Size<<1];
void AddEdge(int u,int v,int t) {
w[++cnt].v=v;
w[cnt].t=t;
w[cnt].next=head[u];
head[u]=cnt;
}
ll dp[Size][Size][3],tmp[Size][3];
int siz[Size];
bool flag[Size];
void dfs(int x,int fa) {
siz[x]=1;
dp[x][1][0]=dp[x][1][1]=dp[x][1][2]=0;
for(int i=head[x]; i; i=w[i].next) {
int nxt=w[i].v;
if(nxt!=fa) {
dfs(nxt,x);
//记录一个临时数组保存答案
memset(tmp,0x3f,sizeof(tmp));
int maxj=min(k,siz[x]);
for(re j=1; j<=maxj; j++) {
int maxk=min(k-j,siz[nxt]);
for(re l=1; l<=maxk; l++) {
//1.x->0 y->0
//x和y中都不包含直径端点,所以x->y的边不在直径上,算两次
tmp[j+l][0]=min(tmp[j+l][0],dp[x][j][0]+dp[nxt][l][0]+(w[i].t<<1));
//2.x->0 y->1
//如果x->y不是直径的一部分,说明y的子树中包含两个直径的端点,不成立
//所以x->y是直径的一部分,算一次
tmp[j+l][1]=min(tmp[j+l][1],dp[x][j][0]+dp[nxt][l][1]+w[i].t);
//3.x->1 y->0
//如果x->y是直径的一部分,说明y中应该也有一个直径端点,不成立
//所以x->y不是直径的一部分,算两次
tmp[j+l][1]=min(tmp[j+l][1],dp[x][j][1]+dp[nxt][l][0]+(w[i].t<<1));
//4.x->0 y->2
//x->y的边显然不在直径上,算两次
tmp[j+l][2]=min(tmp[j+l][2],dp[x][j][0]+dp[nxt][l][2]+(w[i].t<<1));
//5.x->1 y->1
//对于x之前的子树内任意节点u,y的字数内任意节点v,u,v的LCA为x
//所以x->y的边一定在直径上,算一次
tmp[j+l][2]=min(tmp[j+l][2],dp[x][j][1]+dp[nxt][l][1]+w[i].t);
//6.x->2 y->0
//同4,x->y的边一定不在直径上,算两次
tmp[j+l][2]=min(tmp[j+l][2],dp[x][j][2]+dp[nxt][l][0]+(w[i].t<<1));
}
}
for(re j=1; j<=k; j++) {
for(re l=0; l<3; l++) {
dp[x][j][l]=min(dp[x][j][l],tmp[j][l]);
}
}
siz[x]+=siz[nxt];
}
}
}
void Kutori() {
n=read();
k=read();
for(re i=1; i<n; i++) {
int u=read();
int v=read();
int t=read();
AddEdge(u,v,t);
AddEdge(v,u,t);
}
memset(dp,0x3f,sizeof(dp));
dfs(1,0);
ll ans=1e18;
for(re i=1; i<=n; i++) {
//联通块中一定有两个直径端点qwq
ans=min(ans,dp[i][k][2]);
}
printf("%lld",ans);
}
}
int main() {
I_Love::Kutori();
return 0;
}