P2585 [ZJOI2006]三色二叉树(建树+树形DP)

题目链接:[ZJOI2006]三色二叉树 - 洛谷

分析:这道题与其他的与树相关的题不太一样,因为这个题并没有给出具体的点和边,而是只给出了每个节点的孩子个数,所以这也就要求我们需要先建立一棵满足题意的树,然后再进行正常的树形DP,先说一下如何建树:

我们直接每次递归都新建一个节点,然后看该节点有几个孩子,如果没有孩子就直接返回,若只有一个孩子就递归调用其左孩子,如果有两个孩子,就依次递归调用其左右孩子,思路就是这样了,下面看看具体的建树代码:

void build(int x,int len)//建树 
{
	if(idx>len) return ;
	if(s[idx]=='0')	 return ;
	else if(s[idx]=='1')
	{
		l[x]=++idx;
		build(l[x],len);
	}
	else
	{
		l[x]=++idx;
		build(l[x],len);
		r[x]=++idx;
		build(r[x],len);
	}
}

下面来进行动态转移的讲解:

我们令fmax[i][0/1/2]分别表示以i为根的子树中且i为红/绿/蓝时最多被染成绿色的点数
fmin[i][0/1/2]分别表示以i为根的子树中且i为红/绿/蓝时最少被染成绿色的点数

下面只针对求最多被染成绿色的点数进行讲解,最少被染成绿色的点数求法是一样的,只是初始化方面稍有差异,一会我会具体说明

假如我们当前遍历到了节点x,根据x的孩子个数情况我们可以分为三种情况进行讨论:

第一种情况:x无孩子节点,那么显然fmax[x][1]=1,fmax[x][0/2]=0

第二种情况:x只有一个孩子节点,根据我们建树的过程容易发现若节点只有一个孩子节点,那么一定是左孩子,对于f[x][0],也就是当前节点是红色,那么子节点可以是绿色或者蓝色,我们应该取其中的最大值,对于f[x][2],也就是当前节点是蓝色,同理子节点可以是红色或者绿色,我们也是取其中的最大值即可,对于f[x][1],也就是当前节点是绿色,那么子节点可以是红色或者蓝色,但是我们不能仅仅取两者最大值,我们还应该加1,因为当前根节点还有一个绿色节点。

第三种情况:x有两个孩子节点,也就是说当前节点左右孩子均存在,对于f[x][0],当前节点为红色,那么要么左孩子为绿色右孩子为蓝色,要么左孩子为蓝色右孩子为绿色,我们只需要取两者最大值即可,对于f[x][2],当前节点为蓝色,那么要么左孩子为红色右孩子为绿色,要么左孩子为绿色右孩子为红色,我们也是取两者的最大值即可,对于f[x][1],当前节点为绿色,那么要么左孩子为红色右孩子为蓝色,要么左孩子为蓝色右孩子为红色,当然有了第二种情况的分析,这也不仅仅是只取两者最大值,还需要加上当前节点的一个绿色。

具体动态转移代码:

if(l[x]&&r[x])//左右孩子均存在 
	{
		//更新最大值
		fx[x][0]=max(fx[l[x]][1]+fx[r[x]][2],fx[l[x]][2]+fx[r[x]][1]);//根节点为红色则两个孩子分别为绿色和蓝色 
		fx[x][1]=1+max(fx[l[x]][0]+fx[r[x]][2],fx[l[x]][2]+fx[r[x]][0]);//根节点为绿色则两个孩子分别为红色和蓝色(不要忘了加上根节点的绿色) 
		fx[x][2]=max(fx[l[x]][0]+fx[r[x]][1],fx[l[x]][1]+fx[r[x]][0]);//根节点为蓝色则两个孩子分别为绿色和红色
		//下面更新最小值
		fn[x][0]=min(fn[l[x]][1]+fn[r[x]][2],fn[l[x]][2]+fn[r[x]][1]);//根节点为红色则两个孩子分别为绿色和蓝色 
		fn[x][1]=1+min(fn[l[x]][0]+fn[r[x]][2],fn[l[x]][2]+fn[r[x]][0]);//根节点为绿色则两个孩子分别为红色和蓝色(不要忘了加上根节点的绿色) 
		fn[x][2]=min(fn[l[x]][0]+fn[r[x]][1],fn[l[x]][1]+fn[r[x]][0]);//根节点为蓝色则两个孩子分别为绿色和红色
	}
	else if(l[x])//只存在左孩子 
	{
		//更新最大值 
		fx[x][0]=max(fx[l[x]][1],fx[l[x]][2]);//根节点为红色 则左孩子为蓝色或者绿色 
		fx[x][1]=1+max(fx[l[x]][0],fx[l[x]][2]);//根节点为绿色 则左孩子为红色或者蓝色
		fx[x][2]=max(fx[l[x]][0],fx[l[x]][1]);//根节点为蓝色 则左孩子为红色或者绿色
		//下面更新最小值 
		fn[x][0]=min(fn[l[x]][1],fn[l[x]][2]);//根节点为红色 则左孩子为蓝色或者绿色 
		fn[x][1]=1+min(fn[l[x]][0],fn[l[x]][2]);//根节点为绿色 则左孩子为红色或者蓝色
		fn[x][2]=min(fn[l[x]][0],fn[l[x]][1]);//根节点为蓝色 则左孩子为红色或者绿色
	}
	else//没有孩子的情况 
	{
		fx[x][1]=1;//叶子节点为绿色时以该节点为根的子树中绿色点数为1,其余为0
		fn[x][1]=1;//叶子节点为绿色时以该节点为根的子树中绿色点数为1,其余为0
		fn[x][0]=fn[x][2]=0;//由于初始化为正无穷,所以需要手动赋值为0
	}

基本的分析就是这样了,下面我说一下初始化:

对于求最大值的数组fx[][]来说我们需要将其全赋值为0,当遍历到叶子节点x时将f[x][1]更新为1即可。

对于求最小值的数组fn[][]来说我们需要将其全赋值为正无穷大,当遍历到叶子节点x时需要将f[x][1]更新为1,还需要将f[x][0/2]更新为0

以上就是本道题目完整的思路分析了,下面是完整代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=5e5+10;
int l[N],r[N],idx;//l[i]/r[i]分别表示i的左右孩子
int fx[N][3],fn[N][3];
char s[N]; 
//fmax[i][0/1/2]分别表示以i为根的子树中且i为红/绿/蓝时最多被染成绿色的点数
//fmin[i][0/1/2]分别表示以i为根的子树中且i为红/绿/蓝时最少被染成绿色的点数 
void build(int x,int len)//建树 
{
	if(idx>len) return ;
	if(s[idx]=='0')	 return ;
	else if(s[idx]=='1')
	{
		l[x]=++idx;
		build(l[x],len);
	}
	else
	{
		l[x]=++idx;
		build(l[x],len);
		r[x]=++idx;
		build(r[x],len);
	}
}
void dfs(int x)
{
	if(l[x]) dfs(l[x]);
	if(r[x]) dfs(r[x]);
	if(l[x]&&r[x])//左右孩子均存在 
	{
		//更新最大值
		fx[x][0]=max(fx[l[x]][1]+fx[r[x]][2],fx[l[x]][2]+fx[r[x]][1]);//根节点为红色则两个孩子分别为绿色和蓝色 
		fx[x][1]=1+max(fx[l[x]][0]+fx[r[x]][2],fx[l[x]][2]+fx[r[x]][0]);//根节点为绿色则两个孩子分别为红色和蓝色(不要忘了加上根节点的绿色) 
		fx[x][2]=max(fx[l[x]][0]+fx[r[x]][1],fx[l[x]][1]+fx[r[x]][0]);//根节点为蓝色则两个孩子分别为绿色和红色
		//下面更新最小值
		fn[x][0]=min(fn[l[x]][1]+fn[r[x]][2],fn[l[x]][2]+fn[r[x]][1]);//根节点为红色则两个孩子分别为绿色和蓝色 
		fn[x][1]=1+min(fn[l[x]][0]+fn[r[x]][2],fn[l[x]][2]+fn[r[x]][0]);//根节点为绿色则两个孩子分别为红色和蓝色(不要忘了加上根节点的绿色) 
		fn[x][2]=min(fn[l[x]][0]+fn[r[x]][1],fn[l[x]][1]+fn[r[x]][0]);//根节点为蓝色则两个孩子分别为绿色和红色
	}
	else if(l[x])//只存在左孩子 
	{
		//更新最大值 
		fx[x][0]=max(fx[l[x]][1],fx[l[x]][2]);//根节点为红色 则左孩子为蓝色或者绿色 
		fx[x][1]=1+max(fx[l[x]][0],fx[l[x]][2]);//根节点为绿色 则左孩子为红色或者蓝色
		fx[x][2]=max(fx[l[x]][0],fx[l[x]][1]);//根节点为蓝色 则左孩子为红色或者绿色
		//下面更新最小值 
		fn[x][0]=min(fn[l[x]][1],fn[l[x]][2]);//根节点为红色 则左孩子为蓝色或者绿色 
		fn[x][1]=1+min(fn[l[x]][0],fn[l[x]][2]);//根节点为绿色 则左孩子为红色或者蓝色
		fn[x][2]=min(fn[l[x]][0],fn[l[x]][1]);//根节点为蓝色 则左孩子为红色或者绿色
	}
	else//没有孩子的情况 
	{
		fx[x][1]=1;//叶子节点为绿色时以该节点为根的子树中绿色点数为1,其余为0
		fn[x][1]=1;//叶子节点为绿色时以该节点为根的子树中绿色点数为1,其余为0
		fn[x][0]=fn[x][2]=0;//由于初始化为正无穷,所以需要手动赋值为0
	}
}
int main()
{
	scanf("%s",s+1);
	int len=strlen(s+1);
	idx=1;
	build(1,len);
	memset(fn,0x3f,sizeof fn);
	dfs(1);
	printf("%d %d",max(max(fx[1][0],fx[1][1]),fx[1][2]),min(min(fn[1][0],fn[1][1]),fn[1][2]));
	return 0;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值