https://www.luogu.com.cn/problem/P1272
温故知新
思路:
我记得去年的时候看这个题解懵逼了挺多的...今年理清楚了。
通常来说做树形dp的时候一般默认u和u的儿子都是连在一起的。
但是这个题是要删边的。
dp[i] [j] 表示以i为根的子树,保留j个节点,且当前子树与父节点相连,拆掉的最小边数。
所以:
我们初始化的时候dp[i][1],考虑当前子树为最终答案的合法情况,即u要和其儿子的边都断开才是合法dp。因此dp[u][1]=son[u];
在转移过程中,我们看当前儿子取k个,和前面的以u为根取j-k个进行合并,由于开始dp初始化为拆掉的最小边数,于是现在合并的时候这条边是不用拆掉的。所以要在原来的拆边基础上-1,也就是加回这条边。
那么转移就变成了dp[u][j]=max(dp[u][j-k]+dp[v][k]-1)
最后还要注意,我们答案最终算的时候,不只是在根节点dp[1][p],我们默认将u和儿子链接。但是最后答案也可以产生在以i为根的子树获得p的最小拆边次数,再去掉他和父亲的连边来获得答案。也就是边数+1;
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e2+100;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
vector<LL>g[maxn];
LL dp[maxn][maxn];
LL son[maxn];
LL n,p;
void dfs(LL u,LL fa){
dp[u][1]=son[u];
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i];
if(v==fa) continue;
dfs(v,u);
for(LL j=p;j>=1;j--){
for(LL k=1;k<j;k++){
dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1);
}
}
}
}
int main(void){
cin.tie(0);std::ios::sync_with_stdio(false);
cin>>n>>p;
for(LL i=1;i<n;i++){
LL u,v;cin>>u>>v;
g[u].push_back(v);son[u]++;
g[v].push_back(u);
}
memset(dp,0x3f,sizeof(dp));
dfs(1,0);
LL ans=dp[1][p];
for(LL i=1;i<=n;i++){
ans=min(ans,dp[i][p]+1);///dp是默认与父亲相连,应该特判根的情况
}
cout<<ans<<"\n";
return 0;
}