【难度】
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
【解释】
保留圈出来的那两条边,最后总权值和最大,为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]; /// 邻接表 ,<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;
}