题目链接:
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]),其中v1,v2分别是u的左右儿子,当然如果只有左儿子或者没有儿子的情况特判一下即可
,如果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;
}