1. 程式人生 > 其它 >CF EDU 127 E - Preorder

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;
}