【自主训练B:树形DP】E : Binary Apple Tree | URAL - 1018

自主训练B:E题 Binary Apple Tree

【难度】

3.5 / 10 3.5/10 3.5/10
第一次正式写树形DP(太菜了呜呜

【题意】

给你一棵二叉树 N N N 个顶点, 根为1,每个节点的编号为 1 − N 1-N 1N ,各不相同。
每条边有一个权值(苹果数) m i m_i mi
问你:这棵树在切割掉一些边后,最后只剩下 Q Q Q 条边,使得保留的边权和(苹果数)最大?

【注:切割掉后还需要是根为1的一棵树。】

【数据范围】

2 ≤ N ≤ 100 2\le N\le 100 2N100
1 ≤ Q ≤ N − 1 1\le Q\le N-1 1QN1
∑ m i ≤ 3 e 6 \sum m_i \le 3e6 mi3e6

(Time limit:1000 ms)
(Memory limit:65536 kB)

【输入样例】

N Q N\quad Q NQ
每 条 边 链 接 的 两 个 顶 点 编 号 , m i 每条边链接的两个顶点编号,\quad m_i mi
5 2
1 3 1
1 4 10
2 3 20
3 5 20

【输出样例】

21

【解释】

在这里插入图片描述
保留圈出来的那两条边,最后总权值和最大,为21。

【思路】

经典树形DP

数据范围都比较小,说明DP做法可能是可行的,这题还是树形DP的典型题。

我们定义:
d p [   i   ] [   j   ] 表 示 结 点 i 的 子 树 中 保 留 j 条 边 所 能 获 得 的 最 大 收 益 dp[\,i\,][\,j\,]表示结点i的子树中保留j条边所能获得的\pmb{最大收益} dp[i][j]ij最大收益

树形DP主要通过Dfs进行DP转移,但是考虑到里面的权值在,比较麻烦。
我们可以通过一次Dfs预处理,把边权值等价为所对应的点权值
方法比较简单。
从根节点开始Dfs,若经过边 w w w从节点 u u u到节点 v v v,边权为 v a l val val,那么 v v v的点权即为 v a l val val

再考虑状态转移方程:

【目前在节点 i i i,还有 j j j 条边可以保留:】
(1)若 j = 0 j=0 j=0 ,那么到这个 i i i节点就结束了。返回值即为 v a l [ i ] val[i] val[i]
(2)若 j ≥ 1 j\ge1 j1,那么我们把所有边数全部给它的一个子树,然后加上 v a l [ i ] val[i] val[i]取较大者。
(3)若 j ≥ 2 j\ge2 j2,那么我们把一些边数给它的左子树,另外一些边数给它的右子树
然后加上 v a l [ i ] val[i] val[i]取大。

更正规的描述如下:
d p [   i   ] [   j   ] = max ⁡ { v a l [ i ] i f j = 0 max ⁡ { d p [ l e f t _ t r e e ( i ) ] [ j − 1 ] , d p [ r i g h t _ t r e e ( i ) ] [ j − 1 ] }     + v a l [ i ] i f j ≥ 1 max ⁡ j − 2 k = 0 { d p [ l e f t _ t r e e ( i ) ] [ k ] + d p [ r i g h t _ t r e e ( i ) ] [ j − k − 2 ] } + v a l [ i ] i f j ≥ 2 dp[\,i\,][\,j\,]=\max \begin{cases} val[i]\quad if \quad j=0\\ \max\Big \{dp[left\_tree(i)][j-1],dp[right\_tree(i)][j-1]\Big \}\ \ \ + val[i] \quad if\quad j\ge 1\\ \underset{k=0}{\overset{j-2}{\max}}\Big \{ dp[left\_tree(i)][k]+dp[right\_tree(i)][j-k-2]\Big \}+val[i] \quad if\quad j\ge 2 \end{cases} dp[i][j]=maxval[i]ifj=0max{dp[left_tree(i)][j1],dp[right_tree(i)][j1]}   +val[i]ifj1k=0maxj2{dp[left_tree(i)][k]+dp[right_tree(i)][jk2]}+val[i]ifj2

【注意:】
l e f t _ t r e e ( i ) left\_tree(i) left_tree(i)表示 i i i 结点的左子树。右子树同理。

第二行式子,选择一个子树,所以总边数减掉了1。
第三行式子,选择两个子树,所以总边数减掉了2。

【核心代码】

时间复杂度: O ( N × Q ) O(N\times Q) O(N×Q)
记忆化递归之后基本就是状态总数为上限了。

Time(ms):15
Mem(MB):0.4

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
vector<pair<int,int> >G[MAX];		/// 邻接表 ,<pair>第一个存边编号,第二个存链接点

int dp[MAX][MAX];
int tval[MAX];				/// 存边权
int val[MAX];				/// 存转化后的点权

int dfs(int x,int shu,int fa){		/// dp转移
    if(shu == 0){			/// 边界条件
        if(x == -1)return 0;
        return val[x];
    }
    if(x == -1)return -INF;		/// 表示是nil节点,无法选出shu条边
    if(dp[x][shu])return dp[x][shu];	/// 不记忆化递归会T掉的
    int lef = -1;
    int rig = -1;
    if(x == 1){				/// 我的很蠢的写法,找左子树和右子树的编号。
        if(G[x].size()==1)lef = G[x][0].second;
        else lef = G[x][0].second,rig = G[x][1].second;
    }else{
        if(G[x].size()==2){
            if(lef == -1 && G[x][0].second!=fa)lef = G[x][0].second;
            if(lef == -1 && G[x][1].second!=fa)lef = G[x][1].second;
        }
        if(G[x].size()==3){
            if(lef == -1 && G[x][0].second!=fa)lef = G[x][0].second;
            if(lef == -1 && G[x][1].second!=fa)lef = G[x][1].second;
            if(lef != -1 && G[x][1].second!=fa)rig = G[x][1].second;
            if(lef != -1 && G[x][2].second!=fa)rig = G[x][2].second;
        }
    }
    if(shu>=1){
        dp[x][shu] = max(dp[x][shu],max(dfs(lef,shu-1,x),dfs(rig,shu-1,x)) + val[x]);
    }
    if(shu>=2){
        for(int i = 0;i <= shu -2 ; ++i)
            dp[x][shu] = max(dp[x][shu],dfs(lef,i,x) + dfs(rig,shu - i - 2,x) + val[x]);
    }
    return dp[x][shu];
}

void Dfs(int x,int fa){				/// 初始化处理,让边权转化为点权
    for(auto it : G[x]){
        int v = it.second;
        int w = it.first;
        if(v == fa)continue;
        val[v] = tval[w];
        Dfs(v,x);
    }
}

int main()
{
    int n,m;
    cin >> n >> m;
    for(int i=1;i<n;++i){
        int ta,tb,tc;
        cin >> ta >> tb >> tc;
        G[ta].push_back(make_pair(i,tb));
        G[tb].push_back(make_pair(i,ta));
        tval[i] = tc;
    }
    Dfs(1,-1);
    cout << dfs(1,m,-1);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值