题目链接:
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;
}