【XSY2612】Comb Avoiding Trees 生成函数 多项式求逆 矩阵快速幂

该博客探讨了一种特定类型的满二叉树,即k连树,并介绍了如何计算在限制条件下不包含k连树的满二叉树的数量。博主提出使用生成函数和矩阵快速幂的方法来解决这个问题,给出了时间复杂度为O(nlogn+klogk)的解决方案,并提到存在优化到O(klogklogn)的可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

  本题的满二叉树定义为:不存在只有一个儿子的节点的二叉树。

  定义一棵满二叉树 A 包含满二叉树B当且经当 A 可以通过下列三种操作变成B

  • 把一个节点的两个儿子同时删掉
  • 把一棵子树替换成根的的左子树或右子树。

      定义 k 连树为一棵只有恰好k个叶子的满二叉树,如果某个节点有一个右孩子,那么这个右孩子一定是一个叶子。

      对于给定的 k n,对于所有在 1 n之间的 i ,你需要求出所有叶子节点恰好为i,且不包含 k 连树的满二叉树个数。因为答案很大,请对998244353取模。

       n,k130000

题解

  设 fi,j i 个叶子不包含j连树的方案数。

  如果根的左儿子包含 i 连树那么这棵树就会包含i+1连树。

  如果根的右儿子包含 i 连树那么这棵树就会包含i连树。

  所以有:

fi,j=k=1i1fk,j1fik,j

  这是一个 O(n2k) 的DP。

  观察到上面这个东西是个卷积,看看生成函数有没有什么性质:

  设 Fj(x)=i=0fi,jxi

Fj(x)Fj(x)=Fj1(x)Fj(x)+x=x1Fj1(x)

  如果直接多项式求逆是 O(knlogn) 的,很明显会TLE。

  设 Fj(x)=Aj(x)Bj(x)

Fj(x)Aj(x)Bj(x)=x1Aj1(x)Bj1(x)=xBj1(x)Bj1(x)Aj1(x)=xBj1(x)=Bj1(x)Aj1(x)

[Aj(x)Bj(x)]=[Aj1(x)Bj1(x)][0x11]

  那么这就是个常系数线性递推。易证 Ak(x),Bk(x) 的次数不超过 k2 ,所以可以暴力矩乘+FFT做到 O(k2logk) ,也可以把 O(k) 个单位根带进去做一波矩阵快速幂然后用IDFT插回来。

  最后直接求出 Ak(x)Bk(x)1 1 n项就可以了。

  时间复杂度: O(nlogn+klogk)

  zjt:其实可以做到 O(klogklogn) 求一项的。详见zjt大佬的博客

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<cmath>
#include<functional>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void sort(int &a,int &b)
{
    if(a>b)
        swap(a,b);
}
void open(const char *s)
{
#ifndef ONLINE_JUDGE
    char str[100];
    sprintf(str,"%s.in",s);
    freopen(str,"r",stdin);
    sprintf(str,"%s.out",s);
    freopen(str,"w",stdout);
#endif
}
const ll p=998244353;
const ll g=3;
ll fp(ll a,ll b)
{
    ll s=1;
    while(b)
    {
        if(b&1)
            s=s*a%p;
        a=a*a%p;
        b>>=1;
    }
    return s;
}
namespace orzzjt
{
    struct mat
    {
        ll a[2][2];
        mat()
        {
            memset(a,0,sizeof a);
        }
        ll *operator [](int x)
        {
            return a[x];
        }
    };
    mat operator *(mat a,mat b)
    {
        mat c;
        c[0][0]=(a[0][0]*b[0][0]+a[0][1]*b[1][0])%p;
        c[0][1]=(a[0][0]*b[0][1]+a[0][1]*b[1][1])%p;
        c[1][0]=(a[1][0]*b[0][0]+a[1][1]*b[1][0])%p;
        c[1][1]=(a[1][0]*b[0][1]+a[1][1]*b[1][1])%p;
        return c;
    }
    mat pow(mat a,int n)
    {
        mat s;
        s[0][0]=s[1][1]=1;
        while(n)
        {
            if(n&1)
                s=s*a;
            a=a*a;
            n>>=1;
        }
        return s;
    }
    mat a,b;
    pll calc(ll x,int n)
    {
        a[0][0]=0;
        a[0][1]=-1;
        a[1][0]=x;
        a[1][1]=1;
        b[0][0]=0;
        b[0][1]=1;
        b[1][0]=0;
        b[1][1]=0;
        a=pow(a,n-1);
        b=b*a;
        return pll(b[0][0],b[0][1]);
    }
};
namespace ntt
{
    ll w1[270010];
    ll w2[270010];
    int rev[270010];
    int n;
    void init(int m)
    {
        n=m;
        int i;
        for(i=2;i<=n;i<<=1)
        {
            w1[i]=fp(g,(p-1)/i);
            w2[i]=fp(w1[i],p-2);
        }
        rev[0]=0;
        for(i=1;i<n;i++)
            rev[i]=(rev[i>>1]>>1)|(i&1?n>>1:0);
    }
    void ntt(ll *a,int t)
    {
        ll u,v,w,wn;
        int i,j,k;
        for(i=0;i<n;i++)
            if(rev[i]<i)
                swap(a[i],a[rev[i]]);
        for(i=2;i<=n;i<<=1)
        {
            wn=(t==1?w1[i]:w2[i]);
            for(j=0;j<n;j+=i)
            {
                w=1;
                for(k=j;k<j+i/2;k++)
                {
                    u=a[k];
                    v=a[k+i/2]*w%p;
                    a[k]=(u+v)%p;
                    a[k+i/2]=(u-v)%p;
                    w=w*wn%p;
                }
            }
        }
        if(t==-1)
        {
            ll inv=fp(n,p-2);
            for(i=0;i<n;i++)
                a[i]=a[i]*inv%p;
        }
    }
    ll x[270010];
    ll y[270010];
    void copy_clear(ll *a,ll *b,int m)
    {
        int i;
        for(i=0;i<m;i++)
            a[i]=b[i];
        for(i=m;i<n;i++)
            a[i]=0;
    }
    void copy(ll *a,ll *b,int m)
    {
        int i;
        for(i=0;i<m;i++)
            a[i]=b[i];
    }
    void inverse(ll *a,ll *b,int m)
    {
        if(m==1)
        {
            b[0]=fp(a[0],p-2);
            return;
        }
        inverse(a,b,m>>1);
        init(2*m);
        copy_clear(x,a,m);
        copy_clear(y,b,m>>1);
        ntt(x,1);
        ntt(y,1);
        int i;
        for(i=0;i<n;i++)
            x[i]=(2*y[i]%p-x[i]*y[i]%p*y[i]%p+p)%p;
        ntt(x,-1);
        copy(b,x,m);
    }
};
ll w[270010];
ll a[270010];
ll b[270010];
ll c[270010];
int main()
{
    open("b");
    int m,n;
    scanf("%d%d",&m,&n);
    int i;
    int k=1;
    while(k<=(m>>1)+1)
        k<<=1;
    w[0]=1;
    w[1]=fp(g,(p-1)/k);
    for(i=2;i<k;i++)
        w[i]=w[i-1]*w[1]%p;
    ntt::init(k);
    for(i=0;i<k;i++)
    {
        pll s=orzzjt::calc(w[i],m);
        a[i]=s.first;
        b[i]=s.second;
    }
    ntt::ntt(a,-1);
    ntt::ntt(b,-1);
    while(k<=n)
        k<<=1;
    ntt::inverse(b,c,k);
    ntt::init(2*k);
    ntt::ntt(a,1);
    ntt::ntt(c,1);
    for(i=0;i<2*k;i++)
        a[i]=a[i]*c[i]%p;
    ntt::ntt(a,-1);
    for(i=1;i<=n;i++)
    {
        a[i]=(a[i]%p+p)%p;
        printf("%lld\n",a[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值