P1272 重建道路(树形背包)

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;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值