CF755G PolandBall and Many Other Balls

Description

n 个不同的球分成 k 组,每组中有 1 个球,或者两个相邻的球,球可以不被分到组中去

求分组方案数

n109,k215

Solution

一个显然的 DP 式子为设 fi,j 表示 i 个球 j 组的方案数

fi,j=fi1,j+fi1,j1+fi2,j1

加速和 CF623E 类似,直接尝试用 fn,,fn, 转移给 f2n,,具体就分中间的部分是不是另开一组

f2n,k=i+j=kfn,ifn,j+i+j+1=kfn1,ifn1,j

写出 Fi(x)fi,xOGF 不难得到如下转移:

Fi(x)=(x+1)Fi1(x)+xFi2(x)

所以维护 Fn(x),Fn1(x),Fn2(x) 转移到 F2n(x),F2n1(x),F2n2(x) 即可

使用 NTT+ 倍增实现,复杂度 Θ(klogklogn)


梦想不应止步于 log2 的做法,考虑 组合意义

尝试枚举大小为 2 的组的数量,得到一个表达式 ansi=j=0i(ij)(nji)

先保留 j 个球待用,再取出 nj 球中选 i 个球表示被分组,最后在 i 个球中选择 j 个表示这 j 个组大小为 2

由于前面已经保留出来 j 个球,那么可以支持这种操作,将被保留的 j 个球放在选中的球的前面就得到了长度为 n 的序列,重新标号得到分组方案

那么考虑一种在该式子中计算的方案显然在上面的 DP 过程中被计算了

返回来每个 DP 中使用 fi2,k1 转移的方案中的 k1 是一定的;考察使用组合式子得到最终序列的标号是 i 的位置,其前面所有 有 k1 个组(不分球数)的方案也被枚举了,因为这对应了 (nji) 中的第 i 个元素,而其是第 x 个两球组等价于 (ij) 中其位于 其被选中 的所有组合 中 其在第 x 位置上的方案总和

因此其构成了双射,具有正确性(还是这样思考比较清晰一些)

不难发现 j=0i(nji)(ij) 的计算方式保证了每个球只被选择一次,尝试允许重复选择球再容斥,枚举 j 个球被钦定重复选中了

ansi=j=0i(1)j(ij)(njij)2ij

容斥的是选择的 j 个两球组中的和预留的元素重叠的那部分,后面 2ij 表示的是别的球被选重复与否,这样子也就没有 n 个球不够用的问题了

考虑每种重复选择了 k 次的方案都被计算了 j=0k(1)j(kj)=0 次,所以我们得到了正确的答案

直接拆开表示成下降幂就可以卷积了!只要一次哟!

upd: 被提醒下降幂出现了除以 0 的窘况,但是这也是可以分开卷积解决的,鉴于原题数据中没有卡我也懒得改了

复杂度 Θ(klogk)

Code

显然写了一次卷积的容斥做法,不得不说还是挺神的

const int N=6e5+10;
int r[N],W[N],dfac[N],n,k,F[N],G[N],fac[N],ifac[N],inv[N];
inline void NTT(int *f,int lim,int opt){
    for(int i=0;i<lim;++i){
        r[i]=r[i>>1]>>1|((i&1)?(lim>>1):0);
        if(i<r[i]) swap(f[i],f[r[i]]);
    }
    for(int p=2;p<=lim;p<<=1){
        int len=p>>1; W[0]=1;
        W[1]=ksm(3,(mod-1)/p); if(opt==-1) W[1]=ksm(W[1],mod-2);
        rep(i,2,len-1) W[i]=mul(W[i-1],W[1]);
        for(int k=0;k<lim;k+=p){
            for(int l=k;l<k+len;++l){
                int tt=mul(W[l-k],f[l+len]);
                f[l+len]=del(f[l],tt);
                ckadd(f[l],tt);
            }
        }
    }
    if(opt==-1){
        int tmp=ksm(lim,mod-2);
        rep(i,0,lim-1) ckmul(f[i],tmp);
    }
    return ;
}
inline int C(int n,int m){
    if(n<m) return 0;
    return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
signed main(){
    n=read(); k=read(); 
    dfac[0]=1; for(int i=1;i<=k;++i) dfac[i]=mul(dfac[i-1],n-i+1);
    fac[0]=inv[0]=1; rep(i,1,k) fac[i]=mul(fac[i-1],i);
    ifac[k]=ksm(fac[k],mod-2);
    Down(i,k,1) ifac[i-1]=mul(ifac[i],i),inv[i]=mul(fac[i-1],ifac[i]);
    
    for(int i=0,pw=1;i<=k;++i){
        F[i]=mul(ifac[i],ksm(dfac[i],mod-2));
        if(i&1) F[i]=del(0,F[i]);
        G[i]=mul(pw,mul(ifac[i],ifac[i]));
        ckadd(pw,pw);
    }
    
    int lim=1; while(lim<=k*2) lim<<=1;
    NTT(F,lim,1); NTT(G,lim,1);
    rep(i,0,lim-1) ckmul(F[i],G[i]);
    NTT(F,lim,-1);
    rep(i,1,k) print(mul(fac[i],mul(F[i],dfac[i])));
    return 0;
}
posted @   没学完四大礼包不改名  阅读(87)  评论(1编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示