B. 酒杯

静谧幽蓝發表於2024-08-11

題意

給定 \(n\)\(m\),問將 \(m\) 個點隨機放在一個深度為 \(n\) 的滿二叉樹的節點上後,每一層至少有一個點的方案數。


思路

首先,我們發現正著直接算會有一個很麻煩的地方就是若多個點放在同一個點上,那麼方案數就要除上 \(siz!\)

於是我們考慮反著算,即容斥。我們可以欽定哪幾層的節點是不能選的,然後乘上一個容斥係數即為答案。具體來說:

\[\]


#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!!!!

相關文章