P2585 [ZJOI2006]三色二叉树-----树形dp

题目链接:
https://www.luogu.com.cn/problem/P2585#submit

题目大意:给你一颗二叉树,你用三种颜色分别为红,蓝,绿去染色每一个节点,但是染色有规定,规定父节点不能和任何一个儿子节点颜色相同,并且两个儿子的颜色也不能相同,简而言之就是父亲与儿子的节点颜色要两两不同,问你
1.最多能然多少个绿色节点
2.最少能染多少个绿色节点.

分析:
1.首先这道题的建图很关键,选择好的建图方式不至于爆内存,这里推荐使用邻接表建图
2.对于第一问求最大值,我们设f[u][0],表示以u为根节点对于u这个节点选择不染绿色时,子树的最多绿色节点数目,f[u][1]表示u这个节点染绿色时,子树的最多绿色节点数目,那么状态转移就很好想了,如果u这个节点不染绿色,那么子节点有且只有一个为绿色, f [ u ] [ 0 ] = m a x ( f [ v 1 ] [ 1 ] + f [ v 2 ] [ 0 ] , f [ v 1 ] [ 0 ] + f [ v 2 ] [ 1 ] ) , 其 中 v 1 , v 2 分 别 是 u 的 左 右 儿 子 , 当 然 如 果 只 有 左 儿 子 或 者 没 有 儿 子 的 情 况 特 判 一 下 即 可 f[u][0] = max(f[v1][1]+f[v2][0],f[v1][0]+f[v2][1]),其中v1,v2分别是u的左右儿子,当然如果只有左儿子或者没有儿子的情况特判一下即可 f[u][0]=max(f[v1][1]+f[v2][0],f[v1][0]+f[v2][1]),v1v2u
,如果u这个节点染绿色,那么他的儿子都不能染绿色, f [ u ] [ 1 ] = f [ v 1 ] [ 0 ] + f [ v 2 ] [ 0 ] + 1 , 同 理 如 果 只 有 左 儿 子 , 或 者 没 有 儿 子 的 情 况 特 判 一 下 即 可 f[u][1] = f[v1][0]+f[v2][0]+1,同理如果只有左儿子,或者没有儿子的情况特判一下即可 f[u][1]=f[v1][0]+f[v2][0]+1,

3.对于第二问求最小值,我们只需要把max修改为min即可,这不难想象
4.注意:无论是求第一问还是第二问,在dfs的过程中最好用一个数组来记录此状态有没有被计算过,也叫做记忆化搜索,不然很可能爆栈或者超时,避免不必要的重复计算.

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 2e6; 
char s[Maxn];
int tree[Maxn][2];
int f[Maxn][2];
int g[Maxn][2];
int vis[Maxn];//记录记忆化搜索 
int cnt=0;
int pos=0;
int build()
{
    
    
	int now = ++cnt;
	if(s[pos]=='2')
	{
    
    
		++pos;
		tree[now][0] = build();
		++pos;
		tree[now][1] = build();
	}
	else if(s[pos]=='1')
	{
    
    
		++pos;
		tree[now][0] = build();
	}
	return now;
}
void dfs1(int now)
{
    
    
	/*没儿子的情况*/
	if(tree[now][0]==-1&&tree[now][1]==-1)
	{
    
    
		f[now][0] = 0;
		f[now][1] = 1;
		vis[now]++;//标记被访问 
		return ;
	}
	/*只有左儿子的情况*/
	else if(tree[now][0]!=-1&&tree[now][1]==-1)
	{
    
    
		/*如果没被访问过就dfs*/
		if(!vis[tree[now][0]]) dfs1(tree[now][0]);
		/*本节点不选绿色的情况的最大值,子节点可选可不选*/
		f[now][0] = max(f[tree[now][0]][0],f[tree[now][0]][1]);
		/*本节点选择绿色的情况,子节点不能选绿色*/
		f[now][1] = f[tree[now][0]][0]+1;
		vis[now]++;//标记被访问 
		return ;
	}
	/*有两个儿子的情况*/
	else
	{
    
    
		/*如果tree[now][0]这个节点没被访问过就dfs*/
		if(!vis[tree[now][0]]) dfs1(tree[now][0]);
		/*如果tree[now][1]这个节点没被访问过就dfs*/
		if(!vis[tree[now][1]]) dfs1(tree[now][1]);			
		/*本节点不选绿色的情况,两个儿子有且只有一个为绿色*/
		f[now][0] = max(f[tree[now][0]][0]+f[tree[now][1]][1],f[tree[now][0]][1]+f[tree[now][1]][0]);
		/*本节点选择绿色的情况,两个儿子都不能选绿色*/
		f[now][1] = f[tree[now][0]][0]+f[tree[now][1]][0]+1; 
		vis[now]++;//标记被访问 
		return ;
	}
	return ;
}
void dfs2(int now)
{
    
    
	/*没儿子的情况*/
	if(tree[now][0]==-1&&tree[now][1]==-1)
	{
    
    
		f[now][0] = 0;
		f[now][1] = 1;
		vis[now]++;//标记为被访问 
		return ;
	}
	
	/*只有左儿子的情况*/
	else if(tree[now][0]!=-1&&tree[now][1]==-1)
	{
    
    
		/*如果没被访问过就dfs*/
		if(!vis[tree[now][0]]) dfs2(tree[now][0]);
		/*本节点不选绿色的情况的最大值,子节点可选可不选*/
		f[now][0] = min(f[tree[now][0]][0],f[tree[now][0]][1]);
		/*本节点选择绿色的情况,子节点不能选绿色*/
		f[now][1] = f[tree[now][0]][0]+1;
		vis[now]++;//被访问标记 
		return ;
	}
	
	/*有两个儿子的情况*/
	else
	{
    
    
		/*如果没被访问过就dfs*/
		if(!vis[tree[now][0]]) dfs2(tree[now][0]);
		/*如果没被访问过就dfs*/
		if(!vis[tree[now][1]]) dfs2(tree[now][1]);			
		/*本节点不选绿色的情况,两个儿子有且只有一个为绿色*/
		f[now][0] = min(f[tree[now][0]][0]+f[tree[now][1]][1],f[tree[now][0]][1]+f[tree[now][1]][0]);
		/*本节点选择绿色的情况,两个儿子都不能选绿色*/
		f[now][1] = f[tree[now][0]][0]+f[tree[now][1]][0]+1; 
		vis[now]++;//标记被访问 
		return ;
	}
	return ;
}
int main()
{
    
    
	scanf("%s",s);
	memset(tree,-1,sizeof(tree));
	memset(vis,0,sizeof(vis));
	build();
	dfs1(1);//1是根节点,求最大值 
	int Max = max(f[1][0],f[1][1]);

	memset(vis,0,sizeof(vis));//特别注意再算Min的时候要初始化访问标记 
	dfs2(1);
	int Min = min(f[1][0],f[1][1]);
	cout<<Max<<' '<<Min<<'\n';
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/TheWayForDream/article/details/116600789