BNUOJ 51279[组队活动 Large](cdq分治+FFT)

传送门
大意:ACM校队一共有n名队员,从1到n标号,现在n名队员要组成若干支队伍,每支队伍至多有m名队员,求一共有多少种不同的组队方案。两个组队方案被视为不同的,当且仅当存在至少一名队员在两种方案中有不同的队友。
这年头真是……分治FFT都开始烂大街了……
我们来推一推吧
这显然是一个1d1d的DP,用f[i]表示i名队员的方案数

f[i]=j=0i1f[ij1]Cji1

i1个人里面选 j个和 i组队(似乎类似strling数)
然后化一下简,便可得到
f[i]=(i1)!j=0i1f[ij1](ij1)!1j!

一看这不是一个裸的卷积吗?是不是很一颗赛艇!我们就可以用NTT来优化一下了
考虑cdq分治处理 [L,R],我们先递归处理 [L,Mid],然后把 [L,Mid]的f值取出来和 (j!)1来卷一卷。我们就可以得到 [L,Mid] [Mid+1,R]的贡献了。是不是很愉♂快啊!
我不知道其他人怎么写的,比我的慢这么多……
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 300005
#define LL long long
const LL MOD = 998244353LL, g = 3, T = 262144;
int n, k;
LL w[MAXN], inv[MAXN], invf[MAXN], dp[MAXN], x1[MAXN], x2[MAXN], jc[MAXN], tmp;
inline void Swap(LL &a, LL &b) { tmp=a; a=b; b=tmp; }
LL ksm(LL a, LL k) {
    LL ans = 1;
    for(; k; k >>= 1) {
        if(k&1) ans = ans * a % MOD;
        a = a * a % MOD;
    }
    return ans;
}
inline void Init() {
    LL r = ksm(g, (MOD-1)/T); w[0] = 1; invf[0] = inv[0] = invf[1] = inv[1] = jc[1] = 1;
    for(int i = 1; i < T; ++ i) w[i] = w[i-1] * r % MOD;
    for(int i = 2; i < T; ++ i) jc[i] = jc[i-1] * i % MOD;
    for(int i = 2; i < T; ++ i) inv[i] = (MOD-MOD/i) * inv[MOD%i] % MOD;
    for(int i = 2; i < T; ++ i) invf[i] = (invf[i-1]*inv[i]) % MOD;
}
inline void NTT(LL *a, int n, int f) {
    int i, j, k;
    for(i = 0, j = 0; i < n; ++ i) {
        if(i > j) Swap(a[i], a[j]);
        for(k = (n>>1); (j^=k) < k; k >>= 1);
    }
    for(i = 1; i < n; i <<= 1)
        for(j = 0; j < n; j += i<<1)
            for(k = 0; k < i; ++ k) {
                LL x = a[j + k], y = w[T/(i<<1)*k] * a[j + k + i] % MOD;
                a[j + k] = (x + y) % MOD; a[j + k + i] = (x + MOD - y) % MOD;
            }
    LL invn;
    if(-1 == f) for(std::reverse(a+1, a+n), invn = ksm(n, MOD-2), i = 0; i < n; ++ i) a[i]= a[i] * invn % MOD;
}
void cdq(int L, int R) {
    if(L == R) return;
    int mid = (L + R) >> 1;
    cdq(L, mid);
    int len = 1;
    for(; len <= (R-L+1); len <<= 1);
    for(int i = 0; i < len; ++ i) {
        x1[i] = (i < k) ? invf[i] : 0;
        x2[i] = (L + i <= mid) ? dp[L + i] : 0;
    }
    NTT(x1, len, 1); NTT(x2, len, 1);
    for(int i = 0; i < len; ++ i) x1[i] = (x1[i] * x2[i]) % MOD;
    NTT(x1, len, -1);
    for(int i = mid+1; i <= R; ++ i) dp[i] = (dp[i] + x1[i-L-1]*inv[i]) % MOD;
    cdq(mid+1, R);
}
int main() {
    Init(); int T; scanf("%d", &T);
    while(T --) {
        scanf("%d%d", &n, &k);
        memset(dp, 0, sizeof dp);
        dp[0] = 1;
        cdq(0, n);
        printf("%lld\n", dp[n]*jc[n]%MOD);
    }
}

转载于:https://www.cnblogs.com/geng4512/p/5296861.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值