ZJOI2006三色二叉树(树形DP)

题目描述

题目链接

一棵二叉树可以按照如下规则表示成一个由 \(0、1、2\) 组成的字符序列,我们称之为 “二叉树序列 \(S\)”:

\(S= \begin{cases} 0 \ \ \ \ \ \ \ \ \ \ 表示该树没有子节点 \newline 1S_1 \ \ \ \ \ \ 表示该树有一个子节点,S_1 为其子树的二叉树序列 \newline 2S_1S_2 \ \ 表示该树有两个子节点,S_1 和 S_2 分别表示其两个子树的二叉树序列 \end{cases}\)

例如,下图所表示的二叉树可以用二叉树序列 \(S=21200110\) 来表示。

123

你的任务是要对一棵二叉树的节点进行染色。

每个节点可以被染成红色、绿色或蓝色。

并且,一个节点与其子节点的颜色必须不同,如果该节点有两个子节点,那么这两个子节点的颜色也必须不相同。

给定一棵二叉树的二叉树序列,请求出这棵树中最多和最少有多少个点能够被染成绿色。

输入格式

仅有一行,表示一个二叉树序列。

输出格式

只有一行,包含两个数,依次表示最多和最少有多少个点能够被染成绿色。

数据范围

输入序列长度不超过 \(5 \times 10^5\)

输入样例:

1122002010

输出样例:

5 2

解题报告

题意理解

每一个节点可以染三种颜色,红黄绿三种,要求父子节点颜色不同,而且左右儿子之间的颜色也要不同,问有多少个节点可以染色为绿色

探求算法

这是一棵

这棵树父子节点之前存在一定的限制关系

题目要求最值计数

根据上面的这些分析,我们不难得出本题需要用树形DP

算法分析

既然本题要用动态规划,那么我们得严格按照,以下顺序求解本题。

  1. 如何设计状态
  2. 状态如何转移

首先,我们来设计状态。

状态的属性必然是,可以染绿色的节点最多是多少。

接着,根据树形DP最常见的状态设计为:

一个节点,及其子树再满足条件的情况下,答案是多少?

所以我们不难设计出:

\[f[i]为i及其子树,满足染色条件下,可以染成绿色的节点,最多为多少。 \]

这是初级的状态设计,我们还需要通过在分析状态转移的过程中,明确如何完善状态的设计。


接下来,设计状态转移。

分析题目的限制条件,来约束状态转移,使其满足不重复不遗漏。

接下来罗列一下约束条件:

  1. 父与子,节点颜色不同
  2. 左右儿子,两者之间,颜色不同

那么此时我们发现,如果还是初级的状态设计,是没有办法转移的。

因为我们并不知道每个节点的染色情况,那么也无法转移了。

所以我们需要优化状态设计

\[f[i][0/1/2]表示节点i染色为红,黄,绿,在他和他的子树都满足染色条件下,染成绿色节点最多为多少。 \]

这样,我们就彻底完成了状态设计,那么接下来开始状态转移。

如果说:

\[a表示左儿子的染色,son\_a为左儿子序号 \\\\ b表示右儿子的染色,son\_b为右儿子序号 \\\\ now表示当前节点的染色,i为当前节点序号 \]

那么显然

\[a \neq b \neq now\\\\ \]

f[x][i]=max(f[son_a][a]+f[son_b][b],f[son_a][b]+f[son_b][a]);
g[x][i]=min(g[son_a][a]+g[son_b][b],g[son_a][b]+g[son_b][a]);
//f[son_a][a]为左儿子选择颜色a,f[son_b][b]为右儿子选择颜色b
//f[son_a][b]为左儿子选择颜色b,f[son_b][a]为右儿子选择颜色a
//在这里a,b的定义不同于上面,指的是两种颜色,而且满足a,b不是当前节点的颜色
//f[x][i]为当前节点x选择颜色为i的绿色节点最大值,g[x][i]为最小值

如果说只有一个儿子节点,状态转移方程有所不同,但是和上面思路一样,具体可以直接看代码。

参考代码

//My English is poor.The Code maybe have some grammer problems.
//To have a better Font display in Acwing.com,I must choose English.
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+20;
char s[N];
int tot,tree[N][3],f[N][3],g[N][3],Size[N];
void dfs(int root)//Build the Tree
{
    Size[root]=s[root]-'0';//count the number of sons
    if (s[root]>='1')//left son
    {
        tree[root][0]=++tot;
        dfs(tot);
    }
    if (s[root]=='2')//right son
    {
        tree[root][1]=++tot;
        dfs(tot);
    }
}
void tree_DP(int x)
{
    if (Size[x]==0)//The node is a leaf node
    {
        f[x][2]=g[x][2]=1;
        return ;
    }
    for(int i=0; i<Size[x]; i++)//son
        tree_DP(tree[x][i]);
    for(int i=0; i<=2; i++)//color
    {
        int a=(i+1)%3,b=(i+2)%3;//other colors
        int son_a=tree[x][0],son_b=tree[x][1];//other sons
        if (Size[x]==1)//only a son
        {  
            
            f[x][i]=max(f[son_a][a],f[son_a][b]);
            g[x][i]=min(g[son_a][a],g[son_a][b]);
        }
        if (Size[x]==2)//two sons
        {
            f[x][i]=max(f[son_a][a]+f[son_b][b],f[son_a][b]+f[son_b][a]);
            g[x][i]=min(g[son_a][a]+g[son_b][b],g[son_a][b]+g[son_b][a]);
        }
        if (i==2)//the now node chooses the green color
            f[x][i]++,g[x][i]++;
        // printf("%d %d %d\n",x,f[x][i],g[x][i]);
    }
}
inline void out()//Output
{
    int ans_Min=1e9,ans_Max=0;
    for(int i=0; i<=2; i++)
    {
        ans_Min=min(ans_Min,g[1][i]);
        ans_Max=max(ans_Max,f[1][i]);
    }
    printf("%d %d\n",ans_Max,ans_Min);
}
inline void init()//Input
{
    scanf("%s",s+1);
    tot=1;
    dfs(1);
}
signed main()
{
    init();
    tree_DP(1);
    out();
    return 0;
}
posted @ 2020-12-29 16:51  秦淮岸灯火阑珊  阅读(440)  评论(2编辑  收藏  举报