【cdq分治&NTT】BNUOJ51279组队活动

传送门

在这之前先去看看BNUOJ51280是这道题的弱化版。

先附上出题人题解 题解

ans[i] 表示当 n=i 时的答案。
考虑第 i 个人所在队伍的人数为j
那么有

ans[i]=j=0min(i,m1)ans[ij1]Cji1

于是乎弱化版问题这样愉快滴解决辣。

问题是现在 1<=n,m<=100000

ans[i]=j=0min(i,m1)ans[ij1]Cji1

=j=0min(i,m1)ans[ij1](i1)!j!(ij1)!

=(i1)!j=0min(i,m1)ans[ij1](ij1)!/j!

f[i]=ans[i]i!
得到
f[i]=j=0min(i,m1)f[ij1]j!

这就是一个裸的卷积。然后用cdq分治来优化DP。
由于不知道之前的 f[i] ,在cdq分治的时候先递归处理左区间,考虑左区间对右区间的影响,再递归处理右区间。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 131072
#define LL long long int
using namespace std;
const LL mod=998244353;
const LL G=3;

int n, m;
LL dp[MAXN], a[MAXN], b[MAXN];
LL inv[MAXN], invf[MAXN], fac[MAXN];

LL power(LL a,LL pos)
{
    LL ans=1;
    while(pos)
    {
        if(pos&1)ans=ans*a%mod;
        a=a*a%mod;
        pos>>=1;
    }
    return ans;
}

void ntt(LL a[],int n,int f)
{
    for(int i=1, j=0;i<n-1;++i)
    {
        for(int d=n;j^=d>>=1, ~j&d;);
        if(i<j)swap(a[i],a[j]);
    }
    LL w, wn, x, y;
    for(int i=1;i<n;i<<=1)
    {
        wn=power(G,(mod-1)/(i<<1));
        for(int j=0;j<n;j+=i<<1)
        {
            w=1;
            for(int k=0;k<i;++k, w=w*wn%mod)
            {
                x=a[j+k], y=a[i+j+k]*w%mod;
                a[j+k]=(x+y)%mod, a[i+j+k]=(x-y+mod)%mod;
            }
        }
    }
    if(f==-1)
    {
        reverse(a+1,a+n);
        LL inv=power(n,mod-2);
        for(int i=0;i<n;++i)a[i]=a[i]*inv%mod;
    }
}

void solve(int l,int r)
{
    if(l==r)return;
    int mid=l+r>>1;
    solve(l,mid);
    int len=r-l+1, n=1;
    while(n<=len)n<<=1;
    for(int i=0;i<n;++i)
    {
        a[i]=l+i<=mid?dp[l+i]:0;
        b[i]=i<m?invf[i]:0;
    }
    ntt(a,n,1), ntt(b,n,1);
    for(int i=0;i<n;++i)a[i]=a[i]*b[i]%mod;
    ntt(a,n,-1);
    for(int i=mid+1;i<=r;++i)
        dp[i]=(dp[i]+a[i-l-1]*inv[i])%mod;
    solve(mid+1,r);
}

void init()
{
    inv[0]=inv[1]=invf[0]=invf[1]=fac[0]=fac[1]=1;
    for(int i=2;i<=100000;++i)
    {
        inv[i]=-mod/i*inv[mod%i]%mod;
        if(inv[i]<0)inv[i]+=mod;
        invf[i]=invf[i-1]*inv[i]%mod;
        fac[i]=fac[i-1]*i%mod;
    }
}

int main()
{
    init();
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d",&n,&m);
        memset(dp,0,sizeof dp);
        dp[0]=1;
        solve(0,n);
        printf("%lld\n",dp[n]*fac[n]%mod);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值