题意
称非叶节点都有两个儿子的二叉树为满二叉树。称恰有 k k k 个叶节点且任意节点的右儿子为均为叶节点的满二叉树为 k k k 连树。
记二叉树 A A A 包含 B B B 当且仅当 A A A 能通过若干次以下操作变成 B B B:
- 删除某个节点的两个子树;
- 若 x x x 为 y y y 的儿子,用 x x x 的两颗子树代替 y y y 的两棵子树。
问:有多少满二叉树叶子个数为 n n n,且不包含 m m m 连树。答案模 998244353 998244353 998244353。 n , m ≤ 1 0 7 n,m\leq 10^7 n,m≤107,2s。
题解
一见满二叉树的个数,立刻想到卡特兰数,立刻想到 01 序列(括号序列),立刻想到儿子有序的有根树数量,中国人的想像惟在这一层能够如此跃进 然后发现不包含
m
m
m 连树的满二叉树对应出来的有根树深度不超过
m
−
1
m-1
m−1,然后就推不动了……
满二叉树怎么对应上括号序列上面有链接。
下面的 n n n 代表原题中的 2 n − 2 2n-2 2n−2(括号序列的长度), m m m 代表原题中的 m − 1 m-1 m−1。
对于括号序列的计数,一个十分常见的套路是将它转化为折线:(
向右上方走,)
向右下方走,要求不能越过
y
=
0
y=0
y=0,最后到达
(
n
,
0
)
(n,0)
(n,0)。该题中还不允许越过
y
=
m
y=m
y=m。
只要求不越过 y = 0 y=0 y=0 的经典做法为:先忽略限制,总方案数是 ( n n / 2 ) n \choose n/2 (n/2n);考虑所有越过了 y = 0 y=0 y=0 的方案,在折线第一次碰到 y = − 1 y=-1 y=−1 的时候将后半部分关于 y = − 1 y=-1 y=−1 翻折,终点会落在 ( n , − 2 ) (n,-2) (n,−2),于是减去 ( n , − 2 ) (n,-2) (n,−2) 为终点的方案数。
考虑 y = m y=m y=m 的限制时类似,容斥时考虑四类折线:
- − 1 → m + 1 ⋯ − 1 → m + 1 -1\to m+1\dots -1\to m+1 −1→m+1⋯−1→m+1
- m + 1 → − 1 … m + 1 → − 1 m+1\to -1\dots m+1\to -1 m+1→−1…m+1→−1
- − 1 → m + 1 ⋯ − 1 -1\to m+1\dots -1 −1→m+1⋯−1
- − 1 → m + 1 … m + 1 -1\to m+1\dots m+1 −1→m+1…m+1
各自考虑反复翻折后终点落到哪里,将方案数各自乘上合适的容斥系数。
时间复杂度 O ( n ) O(n) O(n)
代码:
#include<bits/stdc++.h>
using namespace std;
int getint(){
int ans=0,f=1;
char c=getchar();
while(c<'0'&&c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
const int N=1e7+10,mod=998244353;
int fac[N<<1],ifac[N<<1];
int C(int n,int m){
if(m<0||m>n)return 0;
return fac[n]*1ll*ifac[n-m]%mod*ifac[m]%mod;
}
int calc(int x,int y){
return C(x,x/2+y/2);
}
int main(){
int n=getint(),m=getint();n=(n-1)*2,m=m-1;
fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*1ll*i%mod;
ifac[0]=ifac[1]=1;for(int i=2;i<=n;i++)ifac[i]=((mod-mod/i)*1ll*ifac[mod%i])%mod;
for(int i=1;i<=n;i++)ifac[i]=ifac[i-1]*1ll*ifac[i]%mod;
int ans=-calc(n,0);//(-1 ~ m-1)*0 与 (m-1 ~ -1)*0 会被重复算
for(int i=0;i<=n;i+=2*m+2){
ans=(ans+2ll*calc(n,i)/*(-1 ~ m-1)*x | (m-1 ~ -1)*x*/
-calc(n,-i-2)/*(-1 ~ m-1)*x ~ -1*/
-calc(n,i+m*2)/*(m-1 ~ -1)*x ~ m-1*/)%mod;
}
cout<<(ans+mod)%mod<<endl;
return 0;
}