【BZOJ4987】Tree(树形dp)
题意:给你一棵 个节点的树找出 个不同的点 ,
使得 最小。
首先有个容易想到的性质:这 个点一定是相邻的,那么选 个点可以看成选 条相连的边。
但关键是我们不是要算一个环的长度,而是一条路径的长度,也就是说不用算从终点再走回到起点的距离,事情就变得麻烦起来。
用树形 dp 求解:设 表示在 子树中,选出 条边的方案数。
其中第 维是 表示这条路径的两个端点都在根节点 上;第 维是 表示这条路径的两个端点一个在根节点 上,另一个在 的子树内;第 维是 表示这条路径的两个端点都在 的子树内,但是这条路径经过 。
那么答案就是 。
然后转移也比较明显,主要是这个时间复杂度的证明。
证法一:
设对于某一个点 ,以 为根的子树大小为 , 的儿子的子树大小分别为 。
那么在 节点的时间是:
然后每两层相消,最后总时间复杂度就是根所代表的子树的大小的平方,即 。
证法二:
dp 的实质可以看做枚举树中的点对 ,然后当且仅当存在某一个 ,使得 和 分别在 的两个儿子的子树中。显然,对于每一个点对 ,有且仅有一个 。所以总时间复杂度是 。
代码如下:
#include<bits/stdc++.h>
#define N 3010
#define ll long long
#define INF 0x7fffffff
using namespace std;
int n,k,size[N];
int cnt,head[N],nxt[N<<1],to[N<<1];
ll w[N<<1],dp[N][N][3];
//dp[0]:根进根出
//dp[1]:根进子树内出
//dp[2]:子树内进子树内出(经过根)
void adde(int u,int v,ll wi)
{
to[++cnt]=v;
w[cnt]=wi;
nxt[cnt]=head[u];
head[u]=cnt;
}
void dfs(int u,int fa)
{
size[u]=1;
dp[u][0][0]=dp[u][0][1]=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa) continue;
dfs(v,u);
for(int j=size[u]-1;j>=0;j--)
{
for(int k=size[v]-1;k>=0;k--)
{
dp[u][j+k+1][0]=min(dp[u][j+k+1][0],dp[u][j][0]+dp[v][k][0]+w[i]*2);
dp[u][j+k+1][1]=min(dp[u][j+k+1][1],min(dp[u][j][0]+dp[v][k][1]+w[i],dp[u][j][1]+dp[v][k][0]+w[i]*2));
dp[u][j+k+1][2]=min(dp[u][j+k+1][2],min(dp[u][j][0]+dp[v][k][2]+w[i]*2,min(dp[u][j][1]+dp[v][k][1]+w[i],dp[u][j][2]+dp[v][k][0]+w[i]*2)));
}
}
size[u]+=size[v];
}
}
int main()
{
memset(dp,127,sizeof(dp));
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++)
{
int u,v;ll w;
scanf("%d%d%lld",&u,&v,&w);
adde(u,v,w),adde(v,u,w);
}
dfs(1,0);
ll ans=INF;
for(int i=1;i<=n;i++)
ans=min(ans,min(dp[i][k-1][1],dp[i][k-1][2]));
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】