神仙題c++
\(CF1237E ~Balanced~ Binary ~Search ~Trees\)spa
題意:設計
須要求出有多少棵點數爲\(n(n\le 10^6)\)點權爲\({1,2,...,n}\)的二叉搜索樹知足:code
\(1):\) 除了最下面一層外,全部層都是滿的;get
\(2):\) 對於每一個點,若是它有左兒子,那麼左兒子的點權和它的奇偶性不一樣;若是它有右兒子,那麼右兒子的點權和它的奇偶性相同。it
答案對\(998244353\)取模class
\(\rm Sol:\)二叉樹
能夠發現定義\(1\)即這棵樹是一個滿二叉樹,因而對於根節點,有除去其以後仍然有其爲一顆滿二叉樹搜索
設知足定義\(2\)的滿二叉樹爲完美樹,則能夠發現因爲原樹爲一棵二叉查找樹,因而其最右邊的點必定爲\(n\),因此有根的奇偶性與\(n\)的奇偶性相同遍歷
不難看出兩個性質:
\(1):\) 一棵完美樹的子樹仍然是完美樹
\(2):\) 因爲權值爲\(1-n\),因此這棵二叉搜索樹的中序遍歷爲\(1-n\),每棵子樹則能夠對應成爲一個區間
考慮假設某一個數\(x\)做爲根,此時有\(x\)的奇偶性與\(n\)相同,則其左子樹的區間能夠表示爲\([1,x-1]\),右子樹區間則能夠表示爲\([x+1,n]\),且有左子樹大小爲\(x-1\),右子樹大小爲\(n-x\),由於\(n\)的奇偶性和\(x\)相同,因此\(n-x\)必然爲偶數,且\(x-1\)的奇偶性與\(x\)相反,因爲左子樹仍然是一棵完美樹,因此再使用上述結論能夠獲得:
一顆完美樹知足其左子樹根的奇偶性與子樹大小相同,而右子樹大小爲偶數
接下來因爲二叉搜索樹只關心相對的大小關係且其某一個子樹能夠被表示成爲一個區間\([l,r]\),因此咱們使用\([1,r-l+1]\)對應替換此樹全部節點對於答案沒有影響,容易發現假設其原先爲一棵完美樹則替換後仍然是一顆完美樹
因此問題與原樹對應的區間編號無關而之和此樹大小有關
接下來考慮將兩顆完美樹合併成爲一顆大完美樹以及其合法性,按照前面的條件咱們能夠獲得合併以後的樹爲完美樹當且僅當:\(1.\)合併以後知足原樹爲滿二叉樹,\(2.\)右子樹大小爲偶數
咱們能夠手玩獲得:大小爲\(1,2,3,4,5\)的完美樹形態以下:
\(size=1:\quad 1\)
\(size=2:\quad 2.left\to 1\)
\(size=3:\) 不存在合法
\(size=4:\)爲樣例
\(size=5:\)僅存在一個,爲:
\(3.left\to 2,2.left\to1,3.right\to 5,5.left \to 4\)
能夠觀察到除去\(size=1\)以外的全部完美樹高度均\(>1\)且不爲滿二叉樹
因爲 性質\(1\) 咱們知道對於一個大小\(>5\)的完美樹,有其最底層仍然是一棵完美樹,換而言之其除去根以後必然是不滿的,因此咱們能夠得出一個可怕的結論:
將兩顆完美樹合併成一棵大小\(>5\)的完美樹當且僅當\(1.):\) 其左右子樹爲完美樹且高度相同,\(2):\)右子樹大小爲偶數
如今咱們能夠設計一個很是粗略的\(dp\)了,令\(dp_{i,j}\)表示大小爲\(i\)的完美樹高度爲\(j\)的時候的方案數,而後利用這個東西轉移,這樣是\(O(n\log n)\)的作法
仔細思考會發現一個更可怕的東西
咱們知道前\(5\)項的高度和方案數(前爲高度,後爲方案數)大體如此:
\((1,1),(2,1),(-,-),(3,1),(3,1)\)
注意到右子樹的大小僅能是\(2\)和\(4\)
對於右子樹\(2\)而言惟一的合併是左\(2\)右\(2\),合併獲得\(5\)
對於右子樹\(4\)而言惟一的合併是左\(4/5\)右\(4\),合併獲得大小爲\(9/10\)的完美樹,高度爲\(4\)
那麼這樣對於大小爲\(6-8\)的樹其均不具備完美樹,因而接下來能夠用的大小僅有\(9/10\)
相似合併能夠發現\(9/10\)僅能經過合併獲得\(20/21\),而後可行的爲\(41/42\)...能夠發現合法的樹均只有\(1\)種
因而只須要拿\(4/5\)做爲初值遞推下去便可,複雜度\(O(\log n)\)
時間\(3s\)和\(n\)只有\(10^6\)應該是爲了放其餘大常數非正解作法過....
\(Code:\)
#include<bits/stdc++.h> using namespace std ; #define rep( i, s, t ) for( register int i = s; i <= t; ++ i ) #define re register int gi() { char cc = getchar() ; int cn = 0, flus = 1 ; while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; } while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ; return cn * flus ; } int n ; signed main() { n = gi() ; if( n == 1 || n == 2 ) puts("1") ; else { int x = 4, y = 5 ; while( max( x, y ) < n ) { int ux = x, uy = y ; if( ux & 1 ) x = ux + uy + 1, y = uy + uy + 1 ; else x = ux + uy + 1, y = ux + ux + 1 ; if( x > y ) swap( x, y ) ; } if( x == n || y == n ) puts("1") ; else puts("0") ; } return 0 ; }