Living-Dream 系列笔记 第51期
树形 dp:在树形结构上的 dp 问题。
\(\texttt{I}\) 类树形 dp:兄弟子树间无数量约束关系。
\(\texttt{II}\) 类树形 dp:兄弟子树间有数量约束关系(即树上背包 / 有依赖的背包)。
本期题目均为 \(\texttt{I}\) 类树形 dp。
T1
令 \(dp_i\) 表示以 \(i\) 为根的子树的“美丽指数”之和最大值。
因为是无根树,所以答案即为 \(\max\{dp_i\}\)。
显然有初始状态 \(dp_i=val_i\)(\(val_i\) 为每朵花的美丽指数)。
对于转移,我们枚举 \(i\) 的所有子树取 \(\max\) 转移即可。
易得转移:
(\(to_i\) 表示 \(i\) 的邻接点)
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,ans=-2147483647;
int v[N],dp[N];
vector<int> G[N];
void dfs(int x,int fa){
dp[x]=v[x];
for(int i:G[x]){
if(i==fa) continue;
dfs(i,x);
dp[x]=max(dp[x],dp[x]+dp[i]);
}
ans=max(ans,dp[x]);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,-1);
cout<<ans;
return 0;
}
T2
典中典。
令 \(dp_{i,0/1}\) 表示选 / 不选 \(i\) 为根的子树的“快乐指数”之和最大值。
因为是有根树,所以答案即为 \(dp_{rt}\)(可以统计入度为 \(0\) 的点即为根 \(rt\))。
显然有初始状态 \(dp_{i,1}=r_i\)。
对于转移,我们枚举 \(i\) 的所有子树进行分讨即可。
易得转移:
(\(to_i\) 表示 \(i\) 的邻接点)
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,ans;
int v[N],in[N],dp[N][2];
vector<int> G[N];
void dfs(int x,int fa){
dp[x][1]=v[x];
for(int i:G[x]){
if(i==fa) continue;
dfs(i,x);
dp[x][0]+=max(dp[i][0],dp[i][1]);
dp[x][1]+=dp[i][0];
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1,u,v;i<n;i++)
cin>>u>>v,G[v].push_back(u),in[u]++;
int rt=0;
for(int i=1;i<=n;i++){
if(!in[i]){ rt=i; break; }
}
dfs(rt,-1);
cout<<max(dp[rt][0],dp[rt][1]);
return 0;
}
T3
令 \(dp_{i,0/1}\) 表示选 / 不选 \(i\) 为根的子树的最少士兵数目。
因为是无根树,所以答案即为 \(\min(dp_{1,0},dp_{1,1})\)。
关于为什么答案只取 \(dp_{1,0/1}\),这是因为我们 dfs 是从 \(1\) 为根开始的,由于要把整个树都算一遍后才能得出答案,因此取 \(1\) 点的 dp 值即可。
显然有初始状态 \(dp_{i,1}=1\)。
对于转移,我们枚举 \(i\) 的所有子树进行分讨即可。
易得转移:
(\(to_i\) 表示 \(i\) 的邻接点)
注意下标 \(+1\)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,dp[N][2];
vector<int> G[N];
void dfs(int x,int fa){
dp[x][1]=1;
for(int i:G[x]){
if(i==fa) continue;
dfs(i,x);
dp[x][0]+=dp[i][1];
dp[x][1]+=min(dp[i][0],dp[i][1]);
}
}
int main(){
cin>>n;
for(int i=1,k,u,v;i<=n;i++){
cin>>u>>k;
for(int j=1;j<=k;j++)
cin>>v,
G[u+1].push_back(v+1),
G[v+1].push_back(u+1);
}
dfs(1,-1);
cout<<min(dp[1][0],dp[1][1]);
return 0;
}
作业 T1
令 \(dp_{i,1/2/3}\) 表示以 \(i\) 为根的子树的染色方案数之和。
因为是无根树,所以答案即为 \(\min(dp_{1,1},dp_{1,2},dp_{1,3})\),理由同 T3。
显然有初始状态 \(dp_{i,1/2/3}=1\);特殊地,若点 \(i\) 已被染色,则 \(dp_{i,1/2/3}=0\)。
对于转移,我们枚举 \(i\) 的所有子树,将它们的方案数相乘,每棵子树将不同颜色的方案数相加即可。
易得转移:
(\(to_i\) 表示 \(i\) 的邻接点)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,MOD=1e9+7;
int n,k,c[N];
int dp[N][4];
vector<int> G[N<<1];
void dfs(int x,int fa){
for(int i=1;i<=3;i++){
if(dp[x][i]){
for(int j=1;j<=3;j++)
if(i!=j) dp[x][j]=0;
break;
}
dp[x][i]=1;
}
for(int i:G[x]){
if(i==fa) continue;
dfs(i,x);
for(int j=1;j<=3;j++){
int s=0;
for(int p=1;p<=3;p++)
if(p!=j) s=(s+dp[i][p])%MOD;
dp[x][j]=(dp[x][j]*s)%MOD;
}
}
}
signed main(){
cin>>n>>k;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=1,b;i<=k;i++) cin>>b>>c[b],dp[b][c[b]]=1;
dfs(1,-1);
cout<<(dp[1][1]+dp[1][2]+dp[1][3])%MOD;
return 0;
}
作业 T2
构思大分讨 /fn 4M出题人 /fn
首先容易发现节点 \(i\) 的左子节点应为 \(i+1\)。
然后开始 dp,
令 \(dp_{i,0/1/2,0/1}\) 表示将 \(i\) 染成 红/绿/蓝 且 以 \(i\) 为根的子树的 最多/最少 的绿色节点。
因为是无根树,所以答案即为 \(\min(dp_{1,0,0},dp_{1,1,0},dp_{1,2,0})\) 和 \(\min(dp_{1,0,1},dp_{1,1,1},dp_{1,2,1})\)。
显然有初始状态:当点 \(i\) 为叶子节点时,\(dp_{i,1,0}=dp_{i,1,1}=1\)。
对于转移,我们枚举 \(i\) 的所有子树进行大♂分♂讨♂即可。
易得(?)转移:
(\(to_i\) 表示 \(i\) 的右子节点)
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
string s;
int tot,v[N];
int dp[N][3][2];
void dfs(int x){
if(s[x]=='0'){
dp[x][1][0]=dp[x][1][1]=1;
return;
}
dfs(++tot);
if(s[x]=='2'){
int k=++tot;
dfs(k);
dp[x][0][0]=min(dp[x+1][1][0]+dp[k][2][0],dp[x+1][2][0]+dp[k][1][0]);
dp[x][1][0]=min(dp[x+1][0][0]+dp[k][2][0]+1,dp[x+1][2][0]+dp[k][0][0]+1);
dp[x][2][0]=min(dp[x+1][0][0]+dp[k][1][0],dp[x+1][1][0]+dp[k][0][0]);
dp[x][0][1]=max(dp[x+1][1][1]+dp[k][2][1],dp[x+1][2][1]+dp[k][1][1]);
dp[x][1][1]=max(dp[x+1][0][1]+dp[k][2][1]+1,dp[x+1][2][1]+dp[k][0][1]+1);
dp[x][2][1]=max(dp[x+1][0][1]+dp[k][1][1],dp[x+1][1][1]+dp[k][0][1]);
}
else{
dp[x][0][0]=min(dp[x+1][1][0],dp[x+1][2][0]);
dp[x][1][0]=min(dp[x+1][0][0],dp[x+1][2][0])+1;
dp[x][2][0]=min(dp[x+1][0][0],dp[x+1][1][0]);
dp[x][0][1]=max(dp[x+1][1][1],dp[x+1][2][1]);
dp[x][1][1]=max(dp[x+1][0][1],dp[x+1][2][1])+1;
dp[x][2][1]=max(dp[x+1][0][1],dp[x+1][1][1]);
}
}
int main(){
cin>>s,s='#'+s;
dfs(++tot);
cout<<max(dp[1][0][1],max(dp[1][1][1],dp[1][2][1]))<<' '<<min(dp[1][0][0],min(dp[1][1][0],dp[1][2][0]));
return 0;
}

浙公网安备 33010602011771号