自主训练B:E题 Binary Apple Tree
3.5 / 10 3.5/10 3.5/10
第一次正式写树形DP(太菜了呜呜 )
给你一棵二叉树, N N N 个顶点, 根为1,每个节点的编号为 1 − N 1-N 1−N ,各不相同。
每条边有一个权值(苹果数) m i m_i mi
问你:这棵树在切割掉一些边后,最后只剩下 Q Q Q 条边,使得保留的边权和(苹果数)最大?
【注:切割掉后还需要是根为1的一棵树。】
2 ≤ N ≤ 100 2\le N\le 100 2≤N≤100
1 ≤ Q ≤ N − 1 1\le Q\le N-1 1≤Q≤N−1
∑ m i ≤ 3 e 6 \sum m_i \le 3e6 ∑mi≤3e6
(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
经典树形DP
数据范围都比较小,说明DP做法可能是可行的,这题还是树形DP的典型题。
我们定义:
d p [ i ] [ j ] 表 示 结 点 i 的 子 树 中 保 留 j 条 边 所 能 获 得 的 最 大 收 益 dp[\,i\,][\,j\,]表示结点i的子树中保留j条边所能获得的\pmb{最大收益} dp[i][j]表示结点i的子树中保留j条边所能获得的最大收益最大收益最大收益
树形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 j≥1,那么我们把所有边数全部给它的一个子树,然后加上 v a l [ i ] val[i] val[i]取较大者。
(3)若 j ≥ 2 j\ge2 j≥2,那么我们把一些边数给它的左子树,另外一些边数给它的右子树
然后加上 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]=max⎩⎪⎪⎨⎪⎪⎧val[i]ifj=0max{dp[left_tree(i)][j−1],dp[right_tree(i)][j−1]} +val[i]ifj≥1k=0maxj−2{dp[left_tree(i)][k]+dp[right_tree(i)][j−k−2]}+val[i]ifj≥2
【注意:】
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]; /// 邻接表 ,第一个存边编号,第二个存链接点
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;
}