传送门
在这之前先去看看BNUOJ51280是这道题的弱化版。
先附上出题人题解 题解
令 ans[i] 表示当 n=i 时的答案。
考虑第 i 个人所在队伍的人数为 j 。
那么有
问题是现在 1<=n,m<=100000
这就是一个裸的卷积。然后用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;
}