CF438E The Child and Binary Tree

本文介绍了一种高效算法,用于计算给定权值集合下,所有可能的不同好二叉树的数量,通过生成函数和多项式运算实现了O(nlogn)的复杂度。

Description

题目链接

如果一个带权有根二叉树的所有节点的权值都在 \(\{c_1,c_2,c_3,...,c_n\}\) 中,那么我们就称它为好的二叉树,并定义其权值为所有点的权值之和。现在你需要对于所有的 \(s\in [1,m]\) 计算出权值为 \(s\) 的不同的好的二叉树的数量,答案对 \(998244353\) 取模

\(1\le n\le 10^5,1\le m\le 10^5,1\le c_i\le 10^5\)

Solution

显然若枚举所有的 \(s\) ,那么我们应该在 \(\log\) 的时间内求出权值为 \(s\) 的二叉树的数量,这是不太现实的

那么我们可以定义一个生成函数 \(G(x)\),用它的 \(i\) 次项系数表示权值为 \(i\) 的不同的好的二叉树的数量,又由于 \(s\in[1,m]\),所以这种方法应该比较可行

考虑怎么求 \(G(x)\)

在这之前我们先设 \(g_i\) 表示权值为 \(i\) 的不同的好的二叉树的数量并考虑如何转移

由于是求二叉树有关的方案,所以递推关系与卡特兰数类似,即
\[ g_i=\sum\limits_{j=1}^{n}\sum\limits_{k=0}^{i-c_j}g_kg_{i-c_j-k}\tag{1} \]
边界 \(g_0=1\)

现在的问题在于这样没法写成生成函数的形式,因为 \((1)\) 式相当于是把 \(\{g_n\}\) 的生成函数卷起来后每隔几位取一个然后再累加到新的一位上的

每隔几位取一个?

这提醒我们可以考虑构造一个函数,使得它乘上 \(\{g_n\}\) 的生成函数后恰好可以实现上述的效果

其实已经不需要构造了,\(\{c_n\}\) 的生成函数 \(C(x)\) 就可以满足我们的要求,即
\[ [x^k]C(x)=[k\in \{c_n\}]\tag{2} \]
至于为什么手动算算就知道了

那么现在就很明朗了,将上述转移写成生成函数的形式
\[ G(x)=C(x)G^2(x)+1\tag{3} \]
因为 \(C(x)\) 是已知的,我们把它看作常数一样的东西,那么移项利用求根公式得到
\[ G(x)=\frac{1\pm \sqrt{1-4C(x)}}{2C(x)}\tag{4} \]
代入 \(x=0\) 判断解,由于 \(C(0)=0​\),那么我们得到最终的解为
\[ G(x)=\frac{1-\sqrt{1-4C(x)}}{2C(x)}\tag{5} \]

但遗憾的是,因为多项式 \(F(x)\) 存在逆元的充要条件是 \([x^0]F(x)\not=0\) ,而 \(C(x)\) 的常数项为 \(0\),所以它并不存在逆元

回归到 \((3)\) 式,既然 \(C(x)\) 不存在逆元,我们就不能让它在分母上,而 \(C(x)\) 是二次项的系数,所以考虑消去 \(G^2(x)\)

因为 \(G(x)\) 存在逆元,所以对两边同除以 \(G(x)\),得
\[ \frac{1}{G(x)}=C(x)+\frac{1}{G^2(x)}\tag{6} \]
换元,设 \(H(x)=G^{-1}(x)​\),那么原式可化为
\[ H^2(x)-H(x)+C(x)=0\tag{7} \]
现在在利用求根公式,得到
\[ H(x)=\frac{1\pm \sqrt{1-4C(x)}}{2}\tag{8} \]
由于 \(H(x)=G^{-1}(x)\),那么必有 \(H(0)=1\),所以最终得到解为
\[ H(x)=\frac{1+\sqrt{1-4C(x)}}{2}\tag{9} \]
那么最后的答案为
\[ G(x)=\frac{2}{1+\sqrt{1-4C(x)}}\tag{10} \]
然后就做完了,复杂度 \(O(n\log n)\)

不得不说中间那步变换还是挺有意思的

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
const int mod=998244353;
const int G=3;
const int invG=332748118;
int n,m,A,k,c[N<<2],f[N<<2],p[N<<2],g[N<<2],d[N<<2],h[N<<2],q[N<<2],v[N<<2];
inline void Add(int &x,int y){x+=y;x-=x>=mod? mod:0;}
inline int MOD(int x){x-=x>=mod? mod:0;return x;}
inline int Minus(int x){x+=x<0? mod:0;return x;}
inline int fas(int x,int p){int res=1;while(p){if(p&1)res=1ll*res*x%mod;p>>=1;x=1ll*x*x%mod;}return res;}
inline void NTT(int *a,int f){
    for(register int i=0,j=0;i<k;i++){
        if(i>j)swap(a[i],a[j]);
        for(register int l=k>>1;(j^=l)<l;l>>=1);}
    for(register int i=1;i<k;i<<=1){
        int w=fas(~f? G:invG,(mod-1)/(i<<1));
        for(register int j=0;j<k;j+=(i<<1)){
            int e=1;
            for(register int p=0;p<i;p++,e=1ll*e*w%mod){
                int x=a[j+p],y=1ll*a[j+p+i]*e%mod;
                a[j+p]=MOD(x+y);a[j+p+i]=MOD(x-y+mod);
            }
        }
    }
}
inline void PINV(int *a,int *b,int deg){
    if(deg==1){b[0]=fas(a[0],mod-2);return;}
    int M=(deg+1)>>1;PINV(a,b,M);
    k=1;while(k<=deg+deg-2)k<<=1;
    for(register int i=0;i<deg;i++)v[i]=a[i];
    for(register int i=deg;i<k;i++)v[i]=0;
    NTT(v,1);NTT(b,1);
    for(register int i=0;i<k;i++)
        b[i]=1ll*(2ll-1ll*v[i]*b[i]%mod+mod)*b[i]%mod;
    NTT(b,-1);int INV=fas(k,mod-2);
    for(register int i=0;i<deg;i++)b[i]=1ll*b[i]*INV%mod;
    for(register int i=deg;i<k;i++)b[i]=0;
}
inline void Sqrt(int *a,int *b,int deg){
    if(deg==1){b[0]=1;return;}
    int M=(deg+1)>>1;Sqrt(a,b,M);
    k=1;while(k<=deg+deg-2)k<<=1;int INV=fas(k,mod-2);
    for(register int i=0;i<deg;i++)p[i]=b[i];
    for(register int i=deg;i<k;i++)p[i]=0;
    NTT(p,1);
    for(register int i=0;i<k;i++)p[i]=1ll*p[i]*p[i]%mod;
    NTT(p,-1);
    for(register int i=0;i<deg;i++)p[i]=1ll*p[i]*INV%mod;
    for(register int i=deg;i<k;i++)p[i]=0;
    for(register int i=0;i<deg;i++)Add(p[i],a[i]);
    for(register int i=0;i<deg;i++)d[i]=MOD(b[i]+b[i]);
    for(register int i=deg;i<k;i++)d[i]=0;
    for(register int i=0;i<k;i++)g[i]=0;
    PINV(d,g,deg);
    k=1;while(k<=deg+deg-2)k<<=1;
    NTT(g,1);NTT(p,1);
    for(register int i=0;i<k;i++)b[i]=1ll*g[i]*p[i]%mod;
    NTT(b,-1);
    for(register int i=0;i<deg;i++)b[i]=1ll*b[i]*INV%mod;
    for(register int i=deg;i<k;i++)b[i]=0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=n;i++)scanf("%d",&A),c[A]++;
    f[0]=1;for(register int i=1;i<=m;i++)f[i]=mod-4*c[i];
    Sqrt(f,h,m+1);h[0]++;PINV(h,q,m+1);
    for(register int i=0;i<=m;i++)q[i]=2ll*q[i]%mod;
    for(register int i=1;i<=m;i++)printf("%d\n",q[i]);
    return 0;
}

转载于:https://www.cnblogs.com/ForwardFuture/p/11538076.html

### Height-Balanced Binary Tree Self-Balanced Binary Tree 的区别 **Height-Balanced Binary Tree** 是一种二叉树,其定义为:对于树中的每个节点,其左右子树的深度之差不超过1。这种平衡性确保了树的整体高度保持在 $ O(\log n) $ 级别,从而保证了查找、插入和删除操作的时间复杂度接近最优。例如,在 LeetCode 题目中,判断一棵二叉树是否是高度平衡的通常涉及递归计算每个节点的左右子树深度,并检查它们的差异[^1]。 **Self-Balanced Binary Tree** 则是一个更广泛的概念。它不仅要求树的高度平衡,还要求在进行插入或删除操作后,树能够通过特定的旋转操作(如左旋、右旋)自动恢复平衡。常见的自平衡二叉搜索树包括 AVL 树 和 红黑树(Red-Black Tree)。AVL 树是一种特殊的高度平衡二叉搜索树,其每个节点的左子树和右子树的高度差最多为1,并且所有操作(插入、删除)都会维持这一性质;而红黑树则通过颜色规则来保证树的大致平衡,虽然它的高度可能略高于 AVL 树,但其插入和删除操作的性能更好[^3]。 #### 关键区别 1. **定义上的区别**: - Height-Balanced Binary Tree 只要求任意节点的左右子树深度差不超过1。 - Self-Balanced Binary Tree 不仅要求高度平衡,还需要支持动态操作(插入、删除)后的自动平衡维护。 2. **应用场景**: - Height-Balanced Binary Tree 通常用于静态结构或不需要频繁更新的场景。 - Self-Balanced Binary Tree 更适合需要频繁插入和删除的动态数据结构,例如数据库索引和语言标准库中的有序集合。 3. **实现机制**: - Height-Balanced Binary Tree 的实现较为简单,只需检查每个节点的子树深度差即可。 - Self-Balanced Binary Tree(如 AVL 树)则需要额外的旋转操作来维持平衡,例如 AVL 树的单旋转和双旋转[^3]。 4. **性能特性**: - 在查找操作较多的情况下,Height-Balanced Binary Tree 和 Self-Balanced Binary Tree 的性能相近。 - 在插入和删除操作较多的情况下,Self-Balanced Binary Tree(如红黑树)通常表现更好,因为它们的平衡策略减少了旋转的次数。 ### 示例代码:AVL 树的插入操作 以下是一个 AVL 树的插入操作示例,展示了如何通过旋转保持树的平衡: ```cpp struct Node { int key; Node *left; Node *right; int height; }; int height(Node *N) { if (N == NULL) return 0; return N->height; } Node* newNode(int key) { Node* node = new Node(); node->key = key; node->left = NULL; node->right = NULL; node->height = 1; return node; } Node* rightRotate(Node *y) { Node *x = y->left; Node *T2 = x->right; x->right = y; y->left = T2; y->height = max(height(y->left), height(y->right)) + 1; x->height = max(height(x->left), height(x->right)) + 1; return x; } Node* leftRotate(Node *x) { Node *y = x->right; Node *T2 = y->left; y->left = x; x->right = T2; x->height = max(height(x->left), height(x->right)) + 1; y->height = max(height(y->left), height(y->right)) + 1; return y; } int getBalance(Node *N) { if (N == NULL) return 0; return height(N->left) - height(N->right); } Node* insert(Node* node, int key) { if (node == NULL) return newNode(key); if (key < node->key) node->left = insert(node->left, key); else if (key > node->key) node->right = insert(node->right, key); else return node; node->height = 1 + max(height(node->left), height(node->right)); int balance = getBalance(node); if (balance > 1 && key < node->left->key) return rightRotate(node); if (balance < -1 && key > node->right->key) return leftRotate(node); if (balance > 1 && key > node->left->key) { node->left = leftRotate(node->left); return rightRotate(node); } if (balance < -1 && key < node->right->key) { node->right = rightRotate(node->right); return leftRotate(node); } return node; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值