zoj cut the tree(树形dp,小细节真的很多)

这道题做的我很是头痛,好不容易思路有了结果调了好几个小时也没调对,太弱,最后是找了份题解“对照”人家代码改的才过,索性把他的思路也直接粘过来了。  一看便知是树形dp,dp[i][j]-表示以节点 i 为根的树砍 j 次得到的最小权值,不过这题难就难在dp[u][0]有双重含义。  1.自己的初始权值。(便于转移)  2.以u为根的树砍 0 次得到的最小权值。  这样直接采取 dp[u][j]=dp[son][k]+dp[u][j-k] 就会出现问题,因为dp[son][0]不一定代表的就是其dp含义。  而且 dp[u][j]=min(dp[u][j],dp[son][0]+dp[u][j]);  如果dp[son][0]>0 的话,它是不会被转移的,而实际情况又是它必须转移。  采取的方式是限制 k>0,那么这样就失去了dp[son][0] 的转移,因为u要连son的所有子树的话dp[son][0]又必须得转移,所以采取的是先将dp[son][0]强制转换过来。  还有一点值得注意的是,如果不要哪棵子树的话,dp[u][j]=dp[son][j-k]  k的范围为[1,num[k]] .   num[k] 以k为根的子树的边。更新答案时,注意如果一个点不是根,那么它至少还得将与父亲节点连的边砍掉,当然还可以砍掉多条边。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
#include<vector>
#define inf -10000005
#define maxn 1005
using namespace std;
vector<int>sons[maxn];
int dp[maxn][30],vis[maxn],num[maxn],k,val[maxn];
void dfs(int rt){
    vis[rt]=1;
    dp[rt][0]=val[rt];
    for(int i=0;i<sons[rt].size();i++){
        int s=sons[rt][i];
        if(vis[s]) continue;
        dfs(s);
        num[rt]+=num[s]+1; //记录当前边的条数
        for(int j=k;j>=0;j--){
            //先将dp[s][0]的值转移过来
            dp[rt][j]+=dp[s][0];
            //更新将s与rt之间的边切掉的情况
            for(int h=1;h<=j;h++)
                dp[rt][j]=max(dp[rt][j],dp[s][h]+dp[rt][j-h]);
            //更新不切掉此边的情况
            for(int h=1;h<=num[s]+1&&h<=j;h++)
                dp[rt][j]=max(dp[rt][j],dp[rt][j-h]+0);
        }
    }
}
int cal(int n){
    int ans=dp[1][k];
    for(int i=2;i<=n;i++)
        for(int j=1;j<=n-1-num[i]&&j<=k;j++) // 在除了以i为根之外的边上砍j刀(j至少为1)
            ans=max(ans,dp[i][k-j]);
    return ans;
}
void init(){
    memset(vis,0,sizeof(vis));
    memset(num,0,sizeof(num));
    for(int i=0;i<maxn;i++)
        for(int j=0;j<=25;j++) dp[i][j]=inf;
}
int main(){
    int i,j,n,a,b;
    while(~scanf("%d%d",&n,&k)){
        for(i=0;i<maxn;i++) sons[i].clear();
        for(i=1;i<=n;i++) scanf("%d",&val[i]);
        for(i=1;i<n;i++){
            scanf("%d%d",&a,&b);
            sons[a].push_back(b);
            sons[b].push_back(a);
        }
        init();
        dfs(1); int ans1=cal(n);
        for(i=1;i<=n;i++) val[i]=-val[i];
        init();
        dfs(1); int ans2=-cal(n);
        cout<<ans2<<" "<<ans1<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值