知らんテクが出てきた…。
https://atcoder.jp/contests/abc222/tasks/abc222_h
問題
正整数Nに対し、以下を満たす根付き木二分木をN次の美しい二分木とする。
- 各頂点0か1が書かれている。
- 葉には1が書かれている。
- 頂点を1つ選び、親または親の親に自身に書かれた数値を加算して、その後頂点に書かれた数値を0にする、という処理をN-1回行うと、根頂点に書かれた数値がN、それ以外は0とできる。
Nに対し、N次の美しい二分木は何通りか。
解法
まず美しい二分木の満たす条件を整理する。
- 最後の処理を行う頂点は、(根頂点以外)もともと1が書かれた頂点のみであり、葉から順に行う。
- 根頂点はもともと1が書かれている。
ここから以下を考える。
f(N) := N次の美しい二分木の組み合わせ数
g(N) := 根頂点の初期値が0で、その子がいずれもa,b次(a+b=N)の美しい二分木であるような組み合わせ数
なので、O(N^2)で良ければこれで求まる。
これを高速化しよう。
数列f(n)・g(n)に対応する母関数をA(x)・B(x)とすると、上記式は
- B = 2A + A^2
- A = x(1+A+B)^2
となる。式変形してBを消すとA = x(1+3A+A^2)^2となる。
これをラグランジュの反転公式を適用すると、
AのN次の項の係数は、(1+3x+x^2)^2N/Nの(N-1)次の項になる。
頑張ってFFTすると、この値を求めることができるもののO(Nlog^2N)かかりTLEしてしまう。
微分を取ってうまく変形すると、(1+3x+x^2)^2N/Nのn次の項の係数U(n)は
となるのでO(N)でU(N-1)を求めればよい。
int N; const ll mo=998244353; ll modpow(ll a, ll n = mo-2) { ll r=1;a%=mo; while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1; return r; } ll dp[10101011][2]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N; dp[0][1]=1; for(i=1;i<=N;i++) { /* // top is 1 for(x=0;x<=i-1;x++) (dp[i][1]+=(dp[x][1]+dp[x][0])*(dp[i-1-x][1]+dp[i-1-x][0]))%=mo; // top is 0 for(x=0;x<=i;x++) (dp[i][0]+=dp[x][1]*dp[i-x][1])%=mo; */ dp[i][1]+=3*(2*N+1-i)*dp[i-1][1]; if(i>=2) dp[i][1]+=(4*N+2-i)*dp[i-2][1]; dp[i][1]=dp[i][1]%mo*modpow(i)%mo; } cout<<dp[N-1][1]*modpow(N)%mo<<endl; }
まとめ
知らない知識だ…。