洛谷

洛谷题单指南-树与图上的动态规划-P2585 [ZJOI2006] 三色二叉树

原题链接:https://www.luogu.com.cn/problem/P2585

题意解读:给二叉树节点染色(红、绿、蓝),父子节点不能同色,兄弟节点不能同色,问最多可以有多少绿色,最少可以有多少绿色。

解题思路:

首先,要根据给定的二叉树序列建树,可以看出二叉树序列是按照DFS顺序给出,序列每个元素代表每个节点有几个子节点,

可以通过栈来维护每个节点的父节点,如果父节点子节点数不为0则建立父子关系,将父节点的子节点数减1,直到0则出栈,这样就可以构建一棵二叉树。

    for(int v = 0; v < str.size(); v++)
    {
        if(stk.size())
        {
            int u = stk.top();
            tr[u].push_back(v);
            if(--sz[u] == 0) stk.pop(); //如果u的子节点数减为0,说明u的子树已经构建完成,可以出栈
        }
        sz[v] = str[v] - '0'; //记录当前节点的子节点数
        if(sz[v]) stk.push(v); //如果当前节点还有子节点,则入栈
    }

然后,通过树形DP来求解,

1、状态表示

设f[i][0]表示以i为根的子树且i是绿色的最大绿色节点数,f[i][1]表示以i为根的子树且i是红色的最大绿色节点数,f[i][2]表示以i为根的子树且i是蓝色的最大绿色节点数。

设g[i][0]表示以i为根的子树且i是绿色的最小绿色节点数,g[i][1]表示以i为根的子树且i是红色的最小绿色节点数,g[i][2]表示以i为根的子树且i是蓝色的最小绿色节点数。

2、状态转移
对于节点u,其左子节点为l,右子节点为r

初始值:
f[u][0] = g[u][0] = 1;
f[u][1] =f[u][2] =g[u][1] =g[u][2] =0;

如果u有1个子节点:
f[u][0] =1+max(f[l][1], f[l][2]);
g[u][0] =1+min(g[l][1], g[l][2]);
f[u][1] =max(f[l][0], f[l][2]);
g[u][1] =min(g[l][0], g[l][2]);
f[u][2] =max(f[l][0], f[l][1]);
g[u][2] =min(g[l][0], g[l][1]);

如果u有2个子节点:
f[u][0] =1+max(f[l][1] +f[r][2], f[l][2] +f[r][1]);
g[u][0] =1+min(g[l][1] +g[r][2], g[l][2] +g[r][1]);
f[u][1] =max(f[l][0] +f[r][2], f[l][2] +f[r][0]);
g[u][1] =min(g[l][0] +g[r][2], g[l][2] +g[r][0]);
f[u][2] =max(f[l][0] +f[r][1], f[l][1] +f[r][0]);
g[u][2] =min(g[l][0] +g[r][1], g[l][1] +g[r][0]);

3、答案
最大值:max(max(f[0][0], f[0][1]), f[0][2])
最小值:min(min(g[0][0], g[0][1]), g[0][2])
100分代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 500005;

vector<int> tr[N];
int f[N][3]; // f[i][0]表示以i为根的子树且i是绿色的最大绿色节点数,f[i][1]表示以i为根的子树且i是红色的最大绿色节点数,f[i][2]表示以i为根的子树且i是蓝色的最大绿色节点数
int g[N][3]; // g[i][0]表示以i为根的子树且i是绿色的最小绿色节点数,g[i][1]表示以i为根的子树且i是红色的最小绿色节点数,g[i][2]表示以i为根的子树且i是蓝色的最小绿色节点数
stack<int> stk; // 栈用于存储当前正在构建的子树的根节点
int sz[N]; // sz[i]表示i的子节点数
string str;

void dfs(int u)
{
    f[u][0] = g[u][0] = 1;
    f[u][1] = f[u][2] = g[u][1] = g[u][2] = 0;

    if(tr[u].size() == 0)  return; // 如果没有子节点,直接返回
    int l, r;
    if(tr[u].size() >= 1) 
    {
        l = tr[u][0]; 
        dfs(l); // 如果左子节点存在,递归处理
        f[u][0] = 1 + max(f[l][1], f[l][2]); 
        g[u][0] = 1 + min(g[l][1], g[l][2]); 
        f[u][1] = max(f[l][0], f[l][2]); 
        g[u][1] = min(g[l][0], g[l][2]);
        f[u][2] = max(f[l][0], f[l][1]);
        g[u][2] = min(g[l][0], g[l][1]); 
    }
    if(tr[u].size() >= 2) 
    {
        r = tr[u][1];
        dfs(r); // 如果右子节点存在,递归处理
        f[u][0] = 1 + max(f[l][1] + f[r][2], f[l][2] + f[r][1]); 
        g[u][0] = 1 + min(g[l][1] + g[r][2], g[l][2] + g[r][1]); 
        f[u][1] = max(f[l][0] + f[r][2], f[l][2] + f[r][0]);
        g[u][1] = min(g[l][0] + g[r][2], g[l][2] + g[r][0]); 
        f[u][2] = max(f[l][0] + f[r][1], f[l][1] + f[r][0]); 
        g[u][2] = min(g[l][0] + g[r][1], g[l][1] + g[r][0]); 
    }
 }

int main()
{
    memset(g, 0x3f, sizeof(g)); // 初始化g数组为最大值
    cin >> str;
    for(int v = 0; v < str.size(); v++)
    {
        if(stk.size())
        {
            int u = stk.top();
            tr[u].push_back(v);
            if(--sz[u] == 0) stk.pop(); //如果u的子节点数减为0,说明u的子树已经构建完成,可以出栈
        }
        sz[v] = str[v] - '0'; //记录当前节点的子节点数
        if(sz[v]) stk.push(v); //如果当前节点还有子节点,则入栈
    }

    dfs(0); 
    cout << max(max(f[0][0], f[0][1]), f[0][2]) 
         << " " 
         << min(min(g[0][0], g[0][1]), g[0][2]);
    return 0;
}