[BZOJ4987]Tree
[BZOJ4987]Tree
题目大意
从前有棵树。找出K个点A1,A2,…,Ak。使得∑dis(AiAi+1),(1<=i<=K-1)最小。
很显然选的部分一定是一个联通快 ,然后考虑走这个联通快的过程
因为走联通快有时需要回头,那么一条边可能经过1次 也可能经过两次
这个联通快的直径是最长的一条路径,由于希望总代价最小,我们沿着直径的一端走,遇到分支就走下去再回来,一直到直径的另一头。这样使直径这条链只被走了一次,让最长的代价最小,然后总代价就最小了(好像不太会证 但这个是对的
发现直径上的边只走一次,非直径上的边走两次。
考虑这个东西怎么树形dp
设dp[i][j][k]表示在i的子树中,选j个点,其中k个点为直径的两端,然后做树形背包即可
大致的讲一下转移过程
对于以u为根的树,枚举他的儿子v,考虑在v之前选了j个点,在v中选了t个点,就可以从
dp[u][j]+dp[v][t]转移到dp[u][j+t]
由于要树形背包记得倒着枚举,第三维通过0/1/2来判断u->v这条边要加几次权即可
最终答案是
\[\min_{i=1}^{n}dp[i][k][2]
\]
讲道理为啥不是dp[1][k][2]呢,求大佬评论讲解一下qwq
#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=x;i<=y;i++)
#define res(i,x,y) for (int i=x;i>=y;i--)
using namespace std;
const int maxn=3010,inf=0x3f3f3f3f;
struct Edge{int v,nex,dis;}edge[maxn<<1];
int head[maxn],cnt=0,n,K;
int dp[maxn][maxn][3],siz[maxn];
inline int read(){
int x=0;char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) x=x*10+ch-48,ch=getchar();
return x;
}
inline void addEdge(int u,int v,int dis){
edge[++cnt]=(Edge){v,head[u],dis};head[u]=cnt;
}
void dfs(int u,int fa){
dp[u][1][0]=dp[u][1][1]=dp[u][0][0]=0;siz[u]=1;
for (int i=head[u];i;i=edge[i].nex){
int v=edge[i].v,w=edge[i].dis;
if (v==fa) continue;
dfs(v,u);
res(j,min(siz[u],K),0)
res(k,min(siz[v],K),0){
dp[u][j+k][0]=min(dp[u][j+k][0],dp[u][j][0]+dp[v][k][0]+2*w);
dp[u][j+k][1]=min(dp[u][j+k][1],dp[u][j][1]+dp[v][k][0]+2*w);
dp[u][j+k][1]=min(dp[u][j+k][1],dp[u][j][0]+dp[v][k][1]+w);
dp[u][j+k][2]=min(dp[u][j+k][2],dp[u][j][2]+dp[v][k][0]+2*w);
dp[u][j+k][2]=min(dp[u][j+k][2],dp[u][j][1]+dp[v][k][1]+w);
dp[u][j+k][2]=min(dp[u][j+k][2],dp[u][j][0]+dp[v][k][2]+2*w);
}
siz[u]+=siz[v];
}
}
int main(){
n=read();K=read();
rep(i,1,n-1){
int u=read(),v=read(),w=read();
addEdge(u,v,w);addEdge(v,u,w);
}
memset(dp,0x3f,sizeof(dp));
dfs(1,0);
int ans=inf;rep(i,1,n) ans=min(ans,dp[i][K][2]);
printf("%d\n",ans);
getchar();getchar();
return 0;
}