题目
题意
给定一棵完全二叉树,每个结点的值是字符 ‘A’,‘B’ 中的其中一个。
你可以进行以下操作若干次:
选定一个结点u ,对于左孩子l 和右孩子 r,将 u的左孩子置为 r,右孩子置为l (或者认为是交换了两棵子树,这会影响遍历顺序)。
随后遍历这棵树,得到一个先序遍历序列 s。求 s 有多少种不同可能。
思路
首先我们有这样一个感性的认识,有以u为根的树,假设左子树的方案有x种,右子树的方案有y种,假设左右子树不同(即无论怎样交换左右子树表示的字符串都不会相同),那么方案数就是xy,假设左右子树相同方案数就是2xy
所以我们就可以递归地去求了,问题主要在于怎么判断两个子树是否相同.
可能有朋友接触过最小表示法,这里的思路类似,假设左子树代表的字符串为s1,右子树代表的字符串为s2,根节点的字母为u,那么表示的字符串只能是s1+u+s2或者s2+u+s1,但是我们可以总是让字典序比较小的那个状态在前面,这样只要是等价的子树形成的字符串都是相同的.比较的时候只需要直接暴力比较了,对于每一层比较的复杂度是O(n)的,所以总体比较的复杂度是O(nlogn)因为最终答案肯定是2的幂,所以我代码中维护的是2的幂数,当然直接维护结果也是可以的
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int inf=0x3f3f3f3f;
typedef long long ll;
const int mod=998244353;
char s[maxn];
int f[maxn];
ll qpow(ll a, ll b)//快速幂
{
ll res = 1;
while (b)
{
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
string dfs(int u)
{
if(!s[u<<1]) //判断是否为叶子节点
return s[u]=='A'?"A":"B";
string s1=dfs(u<<1),s2=dfs(u<<1|1);//左儿子为2*u,右儿子为2*u+1
f[u]=f[u<<1]+f[u<<1|1];//先统计以左右两个儿子为根的,有多少种不同方案
if(s1!=s2)
f[u]++;//再看如果以当前u为根,翻转一下是否一样,如果不一样,答案+1
if(s1<s2)
return s1+s[u]+s2;
else
return s2+s[u]+s1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin>>n>>s + 1;
dfs(1);
cout<<qpow(2,f[1])<<endl;
}