E2. Rubik‘s Cube Coloring (hard version) dp,满二叉树(2300)

在这里插入图片描述
在这里插入图片描述
题意 :

  • 其他条件和上题相同
  • 这里已知n个点,给定每个点的编号和颜色
  • 求这个树的合法染色方案,取模1e9 + 7

思路 :

  • 首先预处理 d [ c ] [ k ] d[c][k] d[c][k]表示根结点颜色为c,深度为k的满二叉树的方案
  • 0 <= c < 6, 1 <= k <= 60,数据范围较小,因此可以很快预处理
  • 如果一颗子树,所有点都没有颜色,那么可以用预处理的数组直接 O ( 1 ) O(1) O(1)给出方案数
  • 因此我们只需要关注有带色点的子树
  • 由于只有2000个点有颜色,k<=60,因此最多2000*60个点是我们需要关注的
  • 我们对这2000*60个点单独记忆化搜索即可
  • d 2 [ i d ] [ c ] d2[id][c] d2[id][c]表示id点颜色为c,子树的方案数
  • d [ ] [ ] d[][] d[][] d 2 [ ] [ ] d2[][] d2[][]的转移 :
  • 枚举左右子结点的颜色即可
  • 注意思考为什么需要映射
#include <iostream>
#include <cstring>
#include <map>

using namespace std;

typedef long long ll;

const ll mod = 1e9 + 7;

ll n, k;
ll lim[10][10];
ll d[10][66];      // 根结点颜色为c,层数为k的满二叉树方案数
ll d2[2222 * 66][6];       // i点颜色为j的子树的方案数
map<ll, ll> col;
map<ll, bool> mark;        // 标记子树中至少有一个点已知颜色的点
map<ll, ll> idx;
ll num;

// 颜色和数字的映射
map<char, ll> c_mp = {
    
    
    {
    
    'w', 0},
    {
    
    'y', 1},
    {
    
    'g', 2},
    {
    
    'b', 3},
    {
    
    'r', 4},
    {
    
    'o', 5},
};
/*
0是白 白黄
1是黄 白黄
2是绿 绿蓝
3是蓝 绿蓝
4是红 红橙
5是橙 红橙
*/

// 对lim数组(两个颜色不能相邻)进行初始化
void init()
{
    
    
    lim[0][0] = lim[0][1] = 1;
    lim[1][0] = lim[1][1] = 1;
    lim[2][3] = lim[2][2] = 1;
    lim[3][2] = lim[3][3] = 1;
    lim[4][5] = lim[4][4] = 1;
    lim[5][4] = lim[5][5] = 1;
}

// 颜色,层数
ll dp_dfs(ll c, ll k)
{
    
    
    if (k == 1) return 1;       // 最后一层(注意这种倒过来表示层数的方法
    
    if (d[c][k] != -1) return d[c][k];          // 记忆化搜索
    
    d[c][k] = 0;    // 开始累加方案
    
    for (int i = 0; i < 6; i ++ )   // 左儿子
    {
    
    
        if (lim[c][i]) continue;    // 这种颜色方案不合法
        for (int j = 0; j < 6; j ++ )       // 右儿子
        {
    
    
            if (lim[c][j]) continue;
            
            // 该子树的根结点颜色为c,且这个根结点位于层数k,子树的方案
            // 子树的方案就是累加左右子树 相乘 的结果
            d[c][k] = (d[c][k] + dp_dfs(i, k - 1) * dp_dfs(j, k - 1) % mod) % mod;
        }
    }
    
    return d[c][k]; // 返回结果
}

// 当前根结点的颜色,当前根结点的层数,当前根结点的编号
ll dfs(ll c, ll k, ll id)
{
    
    
    if (col.count(id) && col[id] != c) return 0;    // 如果当前根结点已知颜色且颜色不是c,则这种方案不合法
    
    if (k == 1) return 1;       // 最后一层
    
    // 如果子树中所有点都没有颜色,直接返回预处理的结果
    if (!mark[id]) return d[c][k];
    
    // 如果至少有一个子结点有颜色,则记忆化搜索
    
    if (!idx.count(id)) idx[id] = ++ num;   // 如果这个点没有被访问过,更新映射
    ll x = idx[id];        // 获取映射后的下标
    if (d2[x][c] != -1) return d2[x][c];    // 记忆化搜索
    
    d2[x][c] = 0;       // 访问到了这个状态,开始给这个状态累加答案
    ll l = id << 1, r = id << 1 | 1;        // 左右儿子,注意这里是用编号,而不是映射
    for (int i = 0; i < 6; i ++ )
    {
    
    
        if (lim[c][i]) continue;
        for (int j = 0; j < 6; j ++ )
        {
    
    
            if (lim[c][j]) continue;
            d2[x][c] = (d2[x][c] + dfs(i, k - 1, l) * dfs(j, k - 1, r) % mod) % mod;
        }
    }
    
    return d2[x][c];
}

void solve()
{
    
    
    init();     // 初始化不能相邻的颜色
    
    cin >> k >> n;
    
    // 初始化,-1表示未曾访问这种方案(记忆化搜索)
    memset(d, -1, sizeof d);
    memset(d2, -1, sizeof d2);
    
    // 子树中没有一个结点有颜色,
    for (int i = 0; i < 6; i ++ )
        dp_dfs(i, k);
        
    // n个有颜色的点
    for (int i = 1; i <= n; i ++ )
    {
    
    
        ll x; string s;
        cin >> x >> s;
        col[x] = c_mp[s[0]];   // id到颜色的映射
        
        while (x)       // O(logn)标记所有祖先结点,这些祖先结点的子树都有有颜色的点
        {
    
    
            mark[x] = 1;
            x /= 2;
        }
    }
    
    ll ans = 0;
    for (int i = 0; i < 6; i ++ )
        ans = (ans + dfs(i, k, 1)) % mod;
    // 方案累加,【颜色,层数,编号】
    
    cout << ans << endl;
}

int main()
{
    
    
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    
    int _ = 1;
//    cin >> _;
    
    while (_ -- )
    {
    
    
        solve();
    }
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_51448653/article/details/121463183