B. 酒杯

题意

给定 \(n\)\(m\),问将 \(m\) 个点随机放在一个深度为 \(n\) 的满二叉树的节点上后,每一层至少有一个点的方案数。


思路

首先,我们发现正着直接算会有一个很麻烦的地方就是若多个点放在同一个点上,那么方案数就要除上 \(siz!\)

于是我们考虑反着算,即容斥。我们可以钦定哪几层的节点是不能选的,然后乘上一个容斥系数即为答案。具体来说:

\[\sum_{S\subseteq \{1,2,...,n\}}(-1)^{|S|}(siz-siz_S)^m \]

就是我们要求的答案。其中 \(|S|\) 代表 \(S\) 的大小。

为了计算方便,我们将 \(S\) 变为一个数,即:

\[\sum_{S=0}^{2^n-1}(-1)^{|S|}(2^n-1-S)^m \]

现在开始化式子时间。

\[\begin{align*} &\sum_{S=0}^{2^n-1}(-1)^{|S|}(2^n-1-S)^m\\ =&\sum_{S=0}^{2^n-1}(-1)^{|2^n-1-S|}(2^n-1-(2^n-1-S))^m\\ =&\sum_{S=0}^{2^n-1}(-1)^{n-|S|}S^m\\ =&(-1)^n\sum_{S=0}^{2^n-1}(-1)^{|S|}S^m\\ \end{align*} \]

现在我们将式子化为了一个简单的形式。我们令 \(f_{i,j}=\sum\limits_{S=0}^{2^i-1}(-1)^{|S|}S^j\),则答案就是 \((-1)^nf_{n,m}\)

现在我们考虑如何递推。对于 \(f_{i+1,j}\),我们希望可以从 \(f_{i,j}\) 推出来。于是:

\[\begin{align*} f_{i+1,j}=&\sum_{S=0}^{2^{i+1}-1}(-1)^{|S|}S^j\\ =&\sum_{S=0}^{2^{i}-1}(-1)^{|S|}S^j+\sum_{S=0}^{2^{i}-1}(-1)^{|S+2^i|}(S+2^i)^j\\ =&\sum_{S=0}^{2^{i}-1}(-1)^{|S|}S^j+(-1)\sum_{S=0}^{2^{i}-1}(-1)^{|S|}(S+2^i)^j\\ =&f_{i,j}-\sum_{S=0}^{2^{i}-1}(-1)^{|S|}(S+2^i)^j\\ =&f_{i,j}-\sum_{S=0}^{2^{i}-1}(-1)^{|S|}\sum_{k=0}^j\binom jkS^{j-k}2^{ik}\\ =&f_{i,j}-\sum_{k=0}^j\binom jk2^{ik}\sum_{S=0}^{2^{i}-1}(-1)^{|S|}S^{j-k}\\ =&f_{i,j}-\sum_{k=0}^j\binom jk2^{ik}f_{i,j-k}\\ \end{align*} \]

于是现在我们得到了一个可以 \(O(m)\) 递推的式子,于是我们可以 \(O(nm^2)\) 的递推出 \(f_{n,m}\),期望 \(25\) pts。

void solve1()
{
    for(int i=0,j; i<maxn; i++) for(j=1,c[i][0]=1; j<=i; j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    f[0][0]=1;
    for(int i=0; i<n; i++)
        for(int j=0; j<=m; j++)
        {
            f[i+1][j]=f[i][j]; int base=power(2,i,mod);
            for(int k=0,pow2=1; k<=j; k++,(pow2*=base)%=mod)
                (f[i+1][j]-=c[j][k]*pow2%mod*f[i][j-k]%mod)%=mod;
            (f[i+1][j]+=mod)%=mod;
        }
    if(n&1) f[n][m]=mod-f[n][m];
    cout<<(f[n][m]%mod+mod)%mod<<'\n';
    return;
}

但是这个复杂度还不够。考虑到既然能从上一层推出这一层,能不能跨行推呢?

于是我们尝试从 \(f_{a}\)\(f_{b}\) 推出 \(f_{a+b}\)。这里钦定 \(a\ge b\)。令 \(S=2^ax+y\)

\[\begin{align*} f_{a+b,j}=&\sum_{S=0}^{2^{a+b}-1}(-1)^{|S|}S^j\\ =&\sum_{x=0}^{2^b-1}\sum_{y=0}^{2^a-1}(-1)^{|2^ax+y|}(2^ax+y)^j\\ =&\sum_{x=0}^{2^b-1}\sum_{y=0}^{2^a-1}(-1)^{|x|+|y|}(2^ax+y)^j\\ =&\sum_{x=0}^{2^b-1}\sum_{y=0}^{2^a-1}(-1)^{|x|}(-1)^{|y|}\sum_{k=0}^j\binom jk(2^ax)^ky^{j-k}\\ =&\sum_{k=0}^j\binom jk2^{ak}\sum_{x=0}^{2^b-1}\sum_{y=0}^{2^a-1}(-1)^{|x|}(-1)^{|y|}x^ky^{j-k}\\ =&\sum_{k=0}^j\binom jk2^{ak}\sum_{x=0}^{2^b-1}(-1)^{|x|}x^k\sum_{y=0}^{2^a-1}(-1)^{|y|}y^{j-k}\\ =&\sum_{k=0}^j\binom jk2^{ak}f_{b,k}f_{a,j-k}\\ \end{align*} \]

于是我们可以用 \(O(m^2)\) 时间跨行推。

那么我们倍增。时间复杂度 \(O(m^2\log n)\)。当然你也可以用 FFT。

对于 \(m\) 特别大的部分分,直接使用 \(f_{i,j}=\sum\limits_{S=0}^{2^i-1}(-1)^{|S|}S^j\) 然后暴力算 \(f_{n,m}\) 就可以了。


代码

#include <bits/stdc++.h>

using namespace std;

#define int long long

#define file(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout);
#define tup tuple<int,int,int,int>
#define pii pair<int,int>
#define pb emplace_back
#define i12 __int128_t
#define mt make_tuple
#define mp make_pair

const int      maxn=2e3+10;
const int      mod=1e9+7;

int c[maxn][maxn],f[maxn][maxn];
int n,m,res[maxn];

int power(int a,int b,int p){int tar=1;for(; b; (a*=a)%=p,b>>=1)if(b&1)(tar*=a)%=p;return tar;}

void conv(int *f,int *g,int lenf,int *res)
{
    static int temp[maxn]; int base=power(2,lenf,mod);
    for(int i=0; i<=m; i++)
    {
        temp[i]=0;
        for(int j=0,pow2=1; j<=i; j++,(pow2*=base)%=mod)
            (temp[i]+=c[i][j]*pow2%mod*g[j]%mod*f[i-j]%mod)%=mod;
    }
    for(int i=0; i<=m; i++) res[i]=temp[i];
    return;
}

void solve1()
{
    for(int i=0,j; i<maxn; i++) for(j=1,c[i][0]=1; j<=i; j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    for(int i=1; i<=m; i++) f[0][i]=mod-1; res[0]=1;
    for(int i=0; i<20; i++) conv(f[i],f[i],1<<i,f[i+1]);
    for(int i=0; i<=20; i++) if((n>>i)&1) conv(f[i],res,1<<i,res);
    if(n&1) res[m]=-res[m];
    cout<<(res[m]%mod+mod)%mod<<'\n';
    return;
}

void solve2()
{
    int tar=0;
    for(int i=0; i<(1<<n); i++)
    {
        int digit=__builtin_popcount(i);
        (tar+=((digit&1)? -1:1)*power(i,m%(mod-1),mod))%=mod;
    }
    if(n&1) tar=-tar;
    return (void)(cout<<((tar%mod+mod)%mod)<<'\n');
}

void work()
{
    /* Code */
    cin>>n>>m;
    if(n>m) return (void)(cout<<0<<'\n');
    if(n<=20) solve2(); else solve1();
    return;
}

signed main()
{
    // file("glass.in","glass.out");
    ios::sync_with_stdio(false); 
    cin.tie(0);
    signed t;
    t=1;
    while(t--)
        work();
    return 0;
} // Texas yyds!!!!
posted @ 2024-08-11 09:41  静谧幽蓝  阅读(12)  评论(0编辑  收藏  举报