CF EDU 127 E - Preorder

E - Preorder

树形dp

若两颗满二叉树深度相同,一颗二叉树交换任意两个兄弟结点及其子树可以得到另一颗二叉树,则成这两颗二叉树同构

设 u 的左右儿子为 ls,rs

  1. 若 ls 与 rs 同构,则 \(ans_u=ans_{ls}*ans_{rs}\)
  2. 否则,ls,rs 交换又是一种新方案,\(ans_u=ans_{ls}*ans_{rs}*2\)

关键为如何判断两颗二叉树同构

性质:设二叉树有 n 个结点,每个结点的子树大小之和是 nlogn 级别

证明:设深度为 h,\(1*(2^h-1)+2*(2^{h-1}-1)+4*(2^{h-2}-1)+...+2^{h-1}*(2-1)=h*2^h-2^h+1\),

因此约为 nlogn

所以可以求出所有结点的子树的字符串,即\(s[u]=str[u]+s[ls]+s[rs]\)并规定左子树字典序 < 右子树,这样直接看两个子树对应的字符串是否相等就可以判断是否同构

求结点深度时如果用 log 函数,注意精度误差

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <map>
#define ls u << 1
#define rs u << 1 | 1
#define log2(x) (log(x) / log(2))
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int mod = 998244353;
const int N = 1 << 19;
const double eps = 1e-8;
int n;
string str, s[N];
ll f[N];

//给出结点编号求其在完全二叉树上的编号
int get_h(int u)
{
	double ans = log2(u + 1);
	//避免精度误差
	if (abs(ans - round(ans)) < eps)
		return ans;
	return ceil(ans);
}
ll dfs(int u)
{
	//判断是否为叶子结点
	if (get_h(u) == n) // if((u << 1) >= (1 << n)) 更好
	{
		s[u] = str[u];
		return 1;
	}
	if (f[u])
		return f[u];
	ll ans = dfs(ls) * dfs(rs) % mod;
	if (s[ls] != s[rs])
	 	ans = ans * 2 % mod;
	if (s[ls] > s[rs])
		swap(s[ls], s[rs]);
	s[u] = str[u] + s[ls] + s[rs];
	return f[u] = ans;
	
}
int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> str;
	str = " " + str;
	cout << dfs(1) << endl;
	return 0;
}
posted @ 2022-05-12 20:39  hzy0227  阅读(26)  评论(0编辑  收藏  举报