【闲话】2023.2.3 k 次加权组合数求和

Page Views Count

问题引入
#

CodeForces-932E Team Work *2400

给出 n,k,求:

i=1nik(ni)modp

1n109,1k5000,p=109+7

k=0
#

二项式定理:

i=1n(ni)=2n1

k=1
#

组合恒等式证明#

根据组合数的定义,可以推出:

(nm)=n!m!(nm)!n×(n1)!m×(m1)!(nm)!=nm(n1m1)

于是:

i=1ni(ni)=i=1ni×ni(n1i1)=ni=0n1(n1i)=n2n1

求导证明#

来自《组合数学》。

先带上一个 x,写成:

i=1n(ni)xi=(1+x)n

两边同时求导:

i=1ni(ni)xi1=n(1+x)n1

代入 x=1 的特殊情况:

i=1ni(ni)=n2n1

k=2
#

再导!

观察发现这个 i 的稳定来源是 xi 项求导,因此在上面代入前的式子再乘上一个 x

i=1ni(ni)xi=n(1+x)n1x

导:

i=1ni2(ni)xi1=n[(n1)(1+x)n2x+(1+x)n1]

代入 x=1 的特殊情况:

i=1ni2(ni)=n[(n1)2n2+2n1]=n(n+1)2n2

《组合数学》:通过交替关于 x 求导并乘以 x,我们可以得到对于任意 k 的恒等式,但随着 k 的增大,将会变得很复杂。

那就先不导剩下情况了。

一个正经的做法
#

i=1nik(ni)=i=1n(ni)j=0k{kj}j!(ij)=j=0k{kj}j!i=1n(ni)(ij)=j=0k{kj}j!i=1n(nj)(njij)=j=0k{kj}nj_i=0nj(nji)=j=0k{kj}nj_2nj

本题可以 O(k2) 预处理第二类 Stirling 数做,在模数友好的情况下可以 NTT 求一行。

一个不正经的猜想
#

Jijidawang:我觉得它是 k 次多项式……

SoyTony:?

Jijidawang:……先除去 2nk

在刚刚的求 k=1,2 时,的确除 2nk 的部分像是 k 次多项式。

同时在 k 一定时,上面正经推导后得出的式子:

fk(n)=(j=0k{kj}nj_2nj)/2nk=j=0k{kj}nj_2kj

似乎也说明这是一个 k 次多项式。

目前我们已知 k=1,2 成立了,做差分试试:

Δfk(n)=fk(n+1)fk(n)=j=0k{kj}2kj[(n+1)j_nj_]=j=0k{kj}2kjnj1_[(n+1)(nj+1)]=j=0kj{kj}2kjnj1_

好像一阶差分可以次数降 1,那么 k 次多项式就板上钉钉了。

最后来一发 Lagrange 插值看看实力。

点击查看代码
int n,k;
inline int q_pow(int A,int B,int P){
    int res=1;
    while(B){
        if(B&1) res=1ll*res*A%P;
        A=1ll*A*A%P;
        B>>=1;
    }
    return res;
}
int Y[5005];
int C[5005][5005],pw[5005];
int fact,fact_inv[5005];
int pw2[5005],inv2=5e8+4;
int pre[5005],suf[5005];
inline int Lagrange(int X){
    pre[0]=1,suf[k]=1;
    for(int i=1;i<=k;++i) pre[i]=1ll*pre[i-1]*(X-(i-1)+mod)%mod;
    for(int i=k-1;i>=0;--i) suf[i]=1ll*suf[i+1]*(X-(i+1)+mod)%mod;
    int res=0;
    for(int i=0;i<=k;++i){
        int now=1ll*Y[i]*pre[i]%mod*suf[i]%mod*fact_inv[i]%mod*fact_inv[k-i]%mod;
        if((k-i)&1) res=(res-now+mod)%mod;
        else res=(res+now)%mod;
    }
    return res;
}
int main(){
    n=read(),k=read(); 
    C[0][0]=1;
    for(int i=1;i<=k;++i){
        C[i][0]=C[i][i]=1;
        for(int j=1;j<i;++j){
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
    }
    for(int i=1;i<=k;++i){
        pw[i]=1;
        for(int j=1;j<=k;++j){
            pw[i]=1ll*pw[i]*i%mod;
        }
    }
    fact=1,fact_inv[0]=1;
    for(int i=1;i<=k;++i) fact=1ll*fact*i%mod;
    fact_inv[k]=q_pow(fact,mod-2,mod);
    for(int i=k-1;i>=1;--i) fact_inv[i]=1ll*fact_inv[i+1]*(i+1)%mod;
    pw2[0]=q_pow(2,k,mod);
    for(int i=1;i<=k;++i) pw2[i]=1ll*pw2[i-1]*inv2%mod;
    for(int i=1;i<=k;++i){
        for(int j=1;j<=i;++j){
            Y[i]=(Y[i]+1ll*C[i][j]*pw[j]%mod)%mod;
        }
        Y[i]=1ll*Y[i]*pw2[i]%mod;
    }
    printf("%lld\n",1ll*Lagrange(n)*q_pow(2,(n-k+mod-1)%(mod-1),mod)%mod);
    return 0;
}

通过了,实锤了。

最终章·导
#

前情提要:全是 Dirty Work,建议不阅读。

k=3 的情况:

[n(n1)(1+x)n2x2+n(1+x)n1x]=n(n1)(n2)(1+x)n3x2+2n(n1)(1+x)n2x+n(n1)(1+x)n2x+n(1+x)n1=n(n1)(n2)(1+x)n3x2+3n(n1)(1+x)n2x+n(1+x)n1

长得还挺工整的,每次求导只增加一项,剩下可以合并。

i=1nik(ni)xi1=i=1kak,i×ni_(1+x)nixi1

乘完了 x,导:

(i=1kak,i×ni_×(1+x)nixi)=i=1kak,i×(ni+1_×(1+x)n(i+1)xi+i×ni_×(1+x)nixi1)=i=1k+1(ak,i1+i×ak,i)×ni_(1+x)nixi1

于是系数满足:

ak+1,i=ak,i1+i×ak,i

换个写法:

{k+1i}={ki1}+i{ki}

非常神奇。

作者:SoyTony

出处:https://www.cnblogs.com/SoyTony/p/Flowers_on_Feb_3rd_2023.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   SoyTony  阅读(192)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示