題意:N個人進行組隊,每個隊不超過M人,求方案數模998244353。
這個遞推方程我都沒想到。。枚舉當前這個人所在隊伍剩餘人數j,則f[i]=Σf[i-1-j]*C(i-1, j),把組合數展開後發現是個卷積的形式,但是不能直接NTT,因爲之前的f值沒有求出來。套用cash一題的策略使用cdq分治解決,先遞歸左邊,再用左邊的更新右邊,再遞歸右邊。注意NTT需要補成2的次冪,常數有點大,但只要保證每次多項式的長度不超過2*(R-L+1)就不會影響時間複雜度。
這樣看來NTT和cdq分治挺容易結合的。。只有出題人稍微構造一下遞推式就可以造一道毒瘤題。。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define erp(i,a,b) for(int i=a;i>=b;--i)
#define LL long long
using namespace std;
const int MAXN = 100010, MAXL = 270000;
const int mo = 998244353, g = 3, T = 262144;
inline void add(int&a, int b) { a+=b; if(a>=mo) a-=mo; }
LL ksm(LL a, LL b)
{
LL r = 1;
for (; b>0; b>>=1, a=a*a%mo)
if (b&1) r = r*a%mo;
return r;
}
int jc[MAXL], rjc[MAXL], inv[MAXL];
void makejc()
{
jc[0] = rjc[0] = inv[0] = 1;
jc[1] = rjc[1] = inv[1] = 1;
rep(i, 2, T)
{
jc[i] = 1ll * i * jc[i-1] % mo;
inv[i] = 1ll * (mo-mo/i) * inv[mo%i] % mo;
rjc[i] = 1ll * rjc[i-1] * inv[i] % mo;
}
}
namespace NTT
{
int r[MAXL], L;
int a[MAXL], b[MAXL], wn[MAXL], invwn[MAXL], invn;
void init()
{
LL r = ksm(g, (mo-1)/T), invr = ksm(r, mo-2);
wn[0] = invwn[0] = 1;
rep(i, 1, T-1)
{
wn[i] = 1ll*wn[i-1]*r%mo;
invwn[i] = 1ll*invwn[i-1]*invr%mo;
}
}
void ntt(int*a, int flag, int N)
{
int *w = (flag==1?wn:invwn);
rep(i, 0, N-1) if (i<r[i]) swap(a[i], a[r[i]]);
for (int i = 1; i<N; i<<=1)
for (int j = 0; j<N; j+=i<<1)
for (int k = 0; k<i; ++k)
{
int x = a[j+k], y = 1ll*w[T/(i<<1)*k]*a[j+k+i]%mo;
a[j+k] = (x+y) % mo;
a[j+k+i] = (x+mo-y) % mo;
}
if (flag<0)
{
invn = ksm(N, mo-2);
rep(i, 0, N-1) a[i] = 1ll*a[i]*invn%mo;
}
}
void mul(int*pol1, int*pol2, int N, int M)
{
rep(i, 0, N) a[i] = pol1[i];
rep(i, 0, M) b[i] = pol2[i];
int tn = N+1, tm = M+1; r[0] = 0;
for (M+=N, L=0, N=1; N<=M; N<<=1) ++L;
rep(i, tn, N) a[i] = 0;
rep(i, tm, N) b[i] = 0;
rep(i, 0, N) r[i] = (r[i>>1]>>1)|((i&1)<<(L-1));
ntt(a, 1, N); ntt(b, 1, N);
rep(i, 0, N) a[i] = 1ll*a[i]*b[i]%mo;
ntt(a, -1, N);
rep(i, 0, M) pol1[i] = a[i];
}
}
int p1[MAXL], p2[MAXL], f[MAXN];
int cas, n, m;
void cdq(int L, int R)
{
if (L==R) return;
int mid = (L+R)>>1, len = R - L + 1;
cdq(L, mid); //f[L...mid] is clear
rep(i, 0, mid-L) p1[i] = 1ll * f[i+L] * rjc[i+L] % mo;
rep(i, mid-L+1, len*2) p1[i] = 0;
rep(i, 0, len) p2[i] = i<m ? rjc[i] : 0;
rep(i, len+1, len*2) p2[i] = 0;
NTT::mul(p1, p2, len, len);
rep(i, mid+1, R) add(f[i], 1ll*p1[i-L-1]*jc[i-1]%mo);
cdq(mid+1, R);
}
int main()
{
NTT::init();
makejc();
scanf("%d", &cas);
while (cas--)
{
scanf("%d%d", &n, &m);
memset(f, 0, sizeof f);
f[0] = 1;
cdq(0, n);
cout << f[n] << '\n';
}
return 0;
}