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\) 转移即可。

易得转移:

\[dp_i=\max(dp_i,dp_i+dp_{to_i}) \]

\(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\) 的所有子树进行分讨即可。

易得转移:

\[\begin{cases} dp_{i,0}=\max(dp_{to_i,0},dp_{to_i,1})\\ dp_{i,1}=dp_{to_i,0} \end{cases} \]

\(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\) 的所有子树进行分讨即可。

易得转移:

\[\begin{cases} dp_{i,0}=dp_{to_i,1}\\ dp_{i,1}=\min(dp_{to_i,0},dp_{to_i,1}) \end{cases} \]

\(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\) 的所有子树,将它们的方案数相乘,每棵子树将不同颜色的方案数相加即可。

易得转移:

\[\begin{cases} dp_{i,1}=\prod_{to_i \in i} dp_{to_i,2}+dp_{to_i,3}\\ dp_{i,2}=\prod_{to_i \in i} dp_{to_i,1}+dp_{to_i,3}\\ dp_{i,3}=\prod_{to_i \in i} dp_{to_i,1}+dp_{to_i,2} \end{cases} \]

\(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\) 的所有子树进行大♂分♂讨♂即可。

易得(?)转移:

\[\begin{cases} dp_{i,0,0}=\min(dp_{i+1,1,0}+dp_{to_i,2,0},dp_{i+1,2,0}+dp_{to_i,1,0})\\ dp_{i,1,0}=\min(dp_{i+1,0,0}+dp_{to_i,2,0}+1,dp_{i+1,2,0}+dp_{to_i,0,0}+1)\\ dp_{i,2,0}=\min(dp_{i+1,0,0}+dp_{to_i,1,0},dp_{i+1,1,0}+dp_{to_i,0,0})\\ dp_{i,0,1}=\max(dp_{i+1,1,1}+dp_{to_i,2,1},dp_{i+1,2,1}+dp_{to_i,1,1})\\ dp_{i,1,1}=\max(dp_{i+1,0,1}+dp_{to_i,2,1}+1,dp_{i+1,2,1}+dp_{to_i,0,1}+1)\\ dp_{i,2,1}=\max(dp_{i+1,0,1}+dp_{to_i,1,1},dp_{i+1,1,1}+dp_{to_i,0,1})\\ \end{cases} \\ \begin{cases} dp_{i,0,0}=\min(dp_{i+1,1,0},dp_{i+1,2,0})\\ dp_{i,1,0}=\min(dp_{i+1,0,0},dp_{i+1,2,0})+1\\ dp_{i,2,0}=\min(dp_{i+1,0,0},dp_{i+1,1,0})\\ dp_{i,0,1}=\max(dp_{i+1,1,1},dp_{i+1,2,1})\\ dp_{i,1,1}=\max(dp_{i+1,0,1},dp_{i+1,2,1})+1\\ dp_{i,2,1}=\max(dp_{i+1,0,1},dp_{i+1,1,1})\\ \end{cases} \]

\(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;
}
posted @ 2024-03-23 23:22  _KidA  阅读(15)  评论(0)    收藏  举报