范德蒙德卷积

范德蒙德卷积(Vandermonde Convolution)是组合数学中的一个重要公式,常用来计算两个组合数的卷积。

定义

给出公式:

\[\sum_{i=0}^{k}\binom{n}{i}\binom{m}{k-i} = \binom{n+m}{k} \]

证明

1.组合意义

先假设这样一个具体情境,有一个长度为 \(n+m\) 的布尔型数组,求出这个数组中有 \(k\)\(1\) 的方案数。

显然,答案是 \(\binom{n+m}{k}\)

但是怎么来推左式呢?

将这个数组分为两个部分,一个是前 \(n\) 个元素,一个是后 \(m\) 个元素

当前面一部分取 \(s\) 个元素,后面一部分取 \(k-s\) 个元素时,根据乘法原理我们可以算出这种情况的答案为 \(\binom{n}{s}\binom{m}{k-s}\)

然后再由加法原理,答案就为 \(\sum_{s=0}^{k}\binom{n}{s}\binom{m}{k-s}\)

2.代数推导

下面给出推导过程,需要使用二项式定理进行展开

\[\begin{aligned} \sum_{r=0}^{n+m}\binom{n+m}{r}x^r &= (x+1)^{n+m}\\ &= (x+1)^n(x+1)^m\\ &= \sum_{p=0}^{n}\binom{n}{p}x^p\sum_{q=0}^{m}\binom{m}{q}x^q\\ &= \sum_{p=0}^{n+m}\sum_{q=0}^{p}\binom{n}{q}\binom{m}{p-q}x^q \end{aligned} \]

应用

[CF785D](Problem - 785D - Codeforces)

经典例题

思路

考虑选择 \(\le p\) 的左括号以及 \(\gt p\) 的右括号的方案数,设 \(p\) 左边的左括号数量为 \(x\) , 右边的右括号的数量为 \(y\)

答案就为

\[\sum_{i=1}^{\min(x,y)}\binom{x}{i}\binom{y}{i}=\binom{x+y}{y} \]

思路就出来了

枚举选择的最右边的左括号的位置,提前处理出左括号前缀和和右括号后缀和,沿用上面的符号,那么方案数就为

\[\sum_{i=0}^{\min(x-1,y-1)}\binom{x-1}{i}\binom{y}{i+1}=\binom{x+y-1}{y-1} \]

由于已经确定了最右边的左括号所以实际可以自由选择的左括号只有 \(x-1\) 个,再预处理一下组合数这道题就解决了

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 2e5+50;
const ll mod = 1e9+7;
int n,pre[N],suf[N];
char s[N];
ll fac[N],infac[N],ans;
inline ll quick_pow(ll x,ll y)
{
    ll res=1;
    while(y)
    {
        if(y&1)res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
inline ll inv(ll x){return quick_pow(x,mod-2);}
inline void init()
{
    fac[0]=1;
    for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    infac[n]=inv(fac[n]);
    for(int i=n-1;i>=0;i--)infac[i]=infac[i+1]*(i+1)%mod;
}
inline ll C(int x,int y){return x<y||y<0?0:fac[x]*infac[y]%mod*infac[x-y]%mod;}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>(s+1);
    n=strlen(s+1);
    init();
    for(int i=1;i<=n;i++)pre[i]=pre[i-1]+(s[i]=='(');
    for(int i=n;i>=1;i--)suf[i]=suf[i+1]+(s[i]==')');
    for(int i=1;i<=n;i++)if(s[i]=='(')ans=(ans+C(pre[i]+suf[i]-1,suf[i]-1))%mod;
    cout<<ans<<endl;
    return 0;
}

[ABC290F](F - Maximum Diameter)

好题,建议读者先思考

思路

由握手定理可知 \(\sum_{i=1}^{n}x_i = 2\times(n-1)\),并且由于图联通所以 \(x_i\) 都为正整数。

先考虑在什么形态下树的直径最长,显然是链

将度数 \(\ge 2\) 的点拿出来构造成一条链,其余的度数为 \(1\) 的点补度数没有满的点,这个时候的直径最长,设度数 $\ge 2 $ 的点的数量为 \(cnt\) 在加上两端度数为 \(1\) 的点,直径为 \(cnt+1\)

枚举度数为 \(1\) 的点,设度为 \(1\) 的点的个数为 \(i\) ,那么剩下没有平的度数就为 \(2\times(n-1)-i\),只需要把这些度数分配给剩余的点。

剩下每个点首先都要分配 \(1\) 的度数,还剩 \(n-2\) 的度数,这是一个经典模型,将 \(n-2\) 分配给剩余的 \(n-i\) 个,并且每个分到的 \(\ge 1\)

考虑插空法,这就相当于在 \(n-2\) 个球中插入 \(n-i-1\) 个空,所以最终的答案就为

\[\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}(n-i+1) \]

但是题目的查询量极大,需要优化,看到两个组合数的特征,容易想到用范德蒙德卷积来优化。

于是原式就可以拆为

\[\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}(n+1)-\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}i=(n+1)\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}-\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}i \]

需要把后面的 \(i\) 给优化掉

这里需要用到等式 \(k\binom{n}{k}=n\binom{n-1}{k-1}\)

只需要把组合数写成阶乘形式就可以证明这个等式

所以原式就可以变为

\[(n+1)\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}-n\sum_{i=2}^{n-1}\binom{n-1}{i-1}\binom{n-2}{n-i-1} \]

再由范德蒙德卷积

\[(n+1)\sum_{i=2}^{n-1}\binom{n}{i}\binom{n-2}{n-i-1}-n\sum_{i=2}^{n-1}\binom{n-1}{i-1}\binom{n-2}{n-i-1}=(n+1)\binom{2n-2}{n-1}-n\binom{2n-3}{n-2} \]

预处理组合数就可以 \(O(1)\) 求出答案

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 2e6+50;
const ll mod = 998244353;
int n;
ll fac[N],infac[N];
inline ll quick_pow(ll x,ll y)
{
  ll res=1;
  while(y)
  {
    if(y&1)res=res*x%mod;
    x=x*x%mod;
    y>>=1;
  }
  return res;
}
inline ll inv(ll x){return quick_pow(x,mod-2);}
inline void init()
{
  fac[0]=1;
  for(int i=1;i<=2000000;i++)fac[i]=fac[i-1]*i%mod;
  infac[2000000]=inv(fac[2000000]);
  for(int i=1999999;i>=0;i--)infac[i]=infac[i+1]*(i+1)%mod;
}
inline ll C(ll x,ll y){return fac[x]*infac[y]%mod*infac[x-y]%mod;}
inline void solve()
{
  cin>>n;
  cout<<((n+1)*C(2*n-3,n-1)%mod-n*C(2*n-4,n-2)%mod+mod)%mod<<'\n';
}

int main()
{
  ios::sync_with_stdio(0);
  cin.tie(0);cout.tie(0);
  init();
  int T;cin>>T;
  while(T--)solve();
  return 0;
}

总结

范德蒙德卷积可以将一些计算的复杂度从 \(O(n)\) 降到 \(O(1)\)

主要运用于知道枚举范围并且各个部分枚举总数为定值的式子

有问题可以评论或者私信,对于博客中的不足之处,欢迎指教

Posted on 2025-09-21 21:59  eternal_silence  阅读(144)  评论(5)    收藏  举报