2/2树形dp学习总结
树上dp就是在树上做dp(如说)
由于树本身具有子结构(子树)的性质,使得树上dp转移比较好推。
今天学习了几个模板问题
最大独立集
在一棵树上选点,每个点有对应的价值,选的点互相之间不能直接连接,求最大价值
状态定义:
\(dp[i,0]\) 为以i为根的子树中,不选i能获得的最大价值
\(dp[i,1]\) 为以i为根的子树中,选i能获得的最大价值
状态转移:
AC code
#include<bits/stdc++.h>
using namespace std;
vector<int> g[6005];
bool v[6005];
int a[6005];
int dp[6005][2];
void dfs(int x){
dp[x][1]=a[x];
for(int i=0;i<g[x].size();i++){
int y=g[x][i];
dfs(y);
dp[x][0]+=max(dp[y][0],dp[y][1]);
dp[x][1]+=dp[y][0];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
g[y].push_back(x);
v[x]=1;
}
int r=0;
for(int i=1;i<=n;i++){
if(!v[i]){
r=i;
break;
}
}
dfs(r);
cout<<max(dp[r][1],dp[r][0]);
return 0;
}
最小支配集
依旧选点,选到的点可以保护与该点距离为1的点,求最少选多少个点可以保护所有的点
状态定义:
\(dp_i\) 表示保护满以 \(i\) 为子树需要的最小点
\(dp_{i,0}\) 为选自己
\(dp_{i,1}\) 为不选,等儿子来保护
\(dp_{i,2}\) 为不选,等父亲来保护
状态转移:
如果存在 \(v\in son(x)\),使得 \(dp[v,0]<dp[v,1]\):
否则:
贪心解
对于当前未被保护的深度最深的节点,显然直接选父亲更好(作用域更广)
但对于保安站岗这种有代价的题就不行,选父节点可能选到代价巨大的点。
AC code
#include<bits/stdc++.h>
using namespace std;
vector<int> g[10005];
int fa[10005];
bool v[10005],z[10005];
int ans;
void dfs(int x,int Fa){
fa[x]=Fa;
for(int i=0;i<g[x].size();i++){
int nx=g[x][i];
if(nx==Fa) continue;
dfs(nx,x);
}
if(!z[x]&&!v[Fa]){
v[Fa]=1;
ans++;
z[x]=z[Fa]=z[fa[Fa]]=1;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n;
cin>>n;
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);
cout<<ans;
return 0;
}
最小点覆盖
还是选点,选到的点可以保护连接的边,问至少选多少个点可以保护所有的边
状态定义:
\(dp[i,0]\) 为以i为根的子树中,不选i需要的最少点
\(dp[i,1]\) 为以i为根的子树中,选i需要的最少点
(像最大独立集)
状态转移
(也像最大独立集,可以理解为最大独立集变式)
666看了 Gallai 恒等式才知道,最小点覆盖+最大独立集=所有点。有点意思
代码实现
void dfs(int u, int fa) {
dp[u][1] = 1;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
dp[u][0] += dp[v][1], dp[u][1] += min(dp[v][0], dp[v][1]);
}
}
最大匹配
从树中选出最多的边,使得任意两条边没有公共点(终于不选点了TwT)
状态定义
虽然是选边,但我们还是根据点来设计(好写)
对于每一个节点,我们只需要考虑选或不选它与子节点相连的边。
\(dp[i,0]\) 表示在i为根的子树中,节点i不与其子节点进行匹配时的最大匹配数。
\(dp[i,1]\) 表示在i为根的子树中,节点i与其子节点进行匹配时的最大匹配数。
状态转移
如果不选,u的子节点可能已经与其他节点匹配了,也可能没匹配,有
如果要选,u匹配的子节点c不可能匹配过,其他的子节点仍可以自由选择,有
优化:
用 \(dp[u,0]\) 的值求 \(dp[u,1]\) 的值,再减去多算的 c 贡献的值即可
代码实现
void dfs(int u, int fa) {
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
dp[u][0] += max(dp[v][0], dp[v][1]);
}
for (auto v : g[u]) {
if (v == fa) continue;
dp[u][1] = max(dp[u][1], dp[u][0] - max(dp[v][0], dp[v][1]) + dp[v][0] + 1);
}
}
写完了!好累QwQ

树形dp总结
o.O
浙公网安备 33010602011771号