题意 :
- 其他条件和上题相同
- 这里已知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;
}