codeforces:G. PolandBall and Many Other Balls

题意:

从n个连续的球中选出m组,每组也要连续且最多有两个,问方案数。

题解:

暴力dp很好写: f[i][j]=f[i1][j]+f[i1][j1]+f[i2][j1] f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] + f [ i − 2 ] [ j − 1 ]
因为n很大,m很小,所以可以考虑倍增+卷积。
所以只要能够从生成函数 fi f i 得到 f2i f 2 ∗ i 就很愉快了。
直接将 f2i f i 2 漏了一种情况,就是少了同时跨过两部分的情况,所以还要加上 f2i1 f i − 1 2
那么考虑怎么维护 f2i1 f 2 ∗ i − 1
直观上是 fi1fi2 f i − 1 ∗ f i ∗ 2 ,然而显然会有重复的情况。
考虑什么情况不会有重复计算的问题,就是当中间的三个球有两个球在同一段。
那么减去剩下的情况就好了,讨论中间的球选不选。
code:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#define LL long long
using namespace std;
LL T,n,bin[70000],m;
const LL p=998244353,yg=3;
LL pow(LL a,LL b)
{
    LL ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%p;
        a=a*a%p;b>>=1;
    }
    return ans;
}
LL g[70000],f[70000],N;
void ntt(LL *a,LL n,LL op)
{
    for(LL i=0;i<n;i++) bin[i]=(bin[i>>1]>>1)|((i&1)*(n>>1));
    for(LL i=0;i<n;i++) if(i<bin[i]) swap(a[i],a[bin[i]]);
    for(LL i=1;i<n;i<<=1)
    {
        LL wn=pow(yg,op==1?(p-1)/(2*i):(p-1)-(p-1)/(2*i)),w,t;
        for(LL j=0;j<n;j+=i<<1)
        {
            w=1;
            for(LL k=0;k<i;k++)
            {
                t=a[i+j+k]*w%p;w=w*wn%p;
                a[i+j+k]=(a[j+k]-t+p)%p;a[j+k]=(a[j+k]+t)%p;
            }
        }
    }
    if(op==-1)
    {
        LL Inv=pow(n,p-2);
        for(LL i=0;i<n;i++) a[i]=a[i]*Inv%p;
    }
}
LL A[70000],B[70000];
LL t1[70000],t2[70000],t3[70000];
void mul()
{
    for(LL i=0;i<N;i++) A[i]=B[i]=0;
    for(LL i=0;i<=m;i++) A[i]=g[i],B[i]=f[i];
    ntt(A,N,1);ntt(B,N,1);
    for(LL i=0;i<N;i++) t1[i]=A[i]*A[i]%p,t2[i]=B[i]*B[i]%p,t3[i]=A[i]*B[i]*2%p;
    ntt(t1,N,-1);ntt(t2,N,-1);ntt(t3,N,-1);
    for(LL i=1;i<=m;i++) g[i]=(t1[i]+t2[i-1])%p,f[i]=(t3[i]-t2[i-1]-t2[i]+p+p)%p;
}
void insert()
{
    for(LL i=m;i>=1;i--)
        f[i]=g[i],g[i]=(g[i]+f[i-1]+g[i-1])%p;
    f[0]=g[0]=1;
}
int main()
{
    scanf("%lld %lld",&n,&m);
    N=1;while(N<=2*m) N<<=1;
    LL b=0;g[0]=1;
    for(LL i=1<<30;i;i>>=1)
    {
        if(b) mul(),b<<=1;
        if(n&i) insert(),b|=1;
    }
    for(LL i=1;i<=m;i++) printf("%lld ",g[i]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值