E2
思路
这题很像皇宫看守这道题;
因为我做过皇宫看守这道题,很自然就想到树形DP;
我们定义 f ( i , j ) f(i,j) f(i,j)表示以 i i i为根的子树,状态为 j j j所拥有的方案数;
当
j
=
0
j=0
j=0表示当前节点的颜色为任意
当
j
=
1
j=1
j=1表示当前节点的颜色为黄、白;
当
j
=
2
j=2
j=2表示当前节点的颜色为绿、蓝;
当
j
=
3
j=3
j=3表示当前节点的颜色为红、橙;
也就是互斥的颜色放在同一组中;
我们设 j j j为 i i i的子节点;
f
(
i
,
0
)
∗
=
f
(
j
,
1
)
+
f
(
j
,
2
)
f(i,0)*=f(j,1)+f(j,2)
f(i,0)∗=f(j,1)+f(j,2)
f
(
i
,
1
)
∗
=
f
(
j
,
2
)
+
f
(
j
,
3
)
f(i,1)*=f(j,2)+f(j,3)
f(i,1)∗=f(j,2)+f(j,3)
f
(
i
,
2
)
∗
=
f
(
j
,
1
)
+
f
(
j
,
3
)
f(i,2)*=f(j,1)+f(j,3)
f(i,2)∗=f(j,1)+f(j,3)
因为这道题的结点数最大有 2 60 − 1 2^{60}-1 260−1这么多,我们不可能全部存下来;
并且这棵树是一颗完美的二叉树,假设父节点为 p p p,那么子节点必然为 2 p 2p 2p, 2 p + 1 2p+1 2p+1;
因此第一维的 i i i我们就可以优化掉,只需要一维用来表示颜色;
因为点数特别多,我们不可能全部扫一遍;
做过Easy
版本就容易想到,后面的子节点如果是任意颜色的;
那么乘一个 4 4 4即可;
因此我们只需要维护部分点,其他点直接算出来;
Code
#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
int dep,k;
const int MOD = 1e9+7;
//点i的颜色是什么
//0表示任意颜色,1表示黄白,2表示绿蓝,3表示红橙
map<ll,int> mp;
vector<ll> dfs(ll u){
vector<ll> dp = {0,2,2,2};
if(mp[u] != 0){
for(int i=1;i<=3;++i) dp[i] = (mp[u] == i);
}
vector<ll> sons = {u<<1,u<<1|1};
for(auto to : sons){
//退出递归
if(mp.count(to)){
auto dp_son = dfs(to);
dp[1] = (dp[1]%MOD*(dp_son[2]+dp_son[3])%MOD)%MOD;
dp[2] = (dp[2]%MOD*(dp_son[1]+dp_son[3])%MOD)%MOD;
dp[3] = (dp[3]%MOD*(dp_son[1]+dp_son[2])%MOD)%MOD;
}
}
return dp;
}
int qpow(ll x,ll y){
ll base = x,ret = 1;
while(y){
if(y&1){
ret *= base;
ret %= MOD;
}
base *= base;
base %= MOD;
y>>=1;
}
return ret%MOD;
}
void solve(){
cin >> dep >> k;
ll id,c;
string color;
while(k--){
cin >> id >> color;
auto &x = color[0];
if(x == 'w' || x == 'y') c = 1;
else if(x == 'g' || x == 'b') c = 2;
else c = 3;
mp[id] = c;
//染色,只访问部分节点
while(id > 1){
id >>= 1;
if(!mp.count(id)) mp[id] = 0;
}
}
auto ans = dfs(1);
ll sum = 0;
for(auto x : ans) sum += x;
ll tot = (1ll<<dep) - 1;
tot -= mp.size();
sum = (sum%MOD*qpow(4,tot)%MOD)%MOD;
cout << sum << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}