@codeforces - [email protected] Bandit Blues

目录

  • @[email protected]
  • @[email protected]
    • @part - [email protected]
    • @part - [email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

求有多少个长度为 n 的排列,从左往右遍历有 a 个数比之前遍历的所有数都大,从右往左遍历有 b 个数比之前遍历的所有数都大。

模 998244323。

input

一行三个整数 n, a, b。1?≤?n?≤?10^5,0?≤?A,?B?≤?n。

output

输出排列数模 998244353。

sample input

5 2 2

sample output

22

@[email protected]

@part - [email protected]

首先从左往右和从右往左都会在最大值的地方停下来。

我们枚举最大值的位置,并记 dp(i, j) 表示 i 个元素顺序遍历有 j 个符合要求的元素的方案数。

则:

\[ans=\sum_{i=1}^{n}dp(i-1, a-1)*dp(n-i, b-1)*C(n-1,i-1)\]

为什么要减去 1 呢?因为我们的最后一个元素一定是最大值。

考虑怎么求解 dp(i, j)。我们为了避免繁杂的枚举,直接考虑 i 个元素中最小的那个元素的位置。如果最小的元素是第一个,则它一定被计算进去,剩下的状态变为 dp(i-1, j-1);否则,它一定不会被计算进去,就可以删除它,变为 dp(i-1, j)。

故:

\[dp(i, j) = dp(i-1, j-1) + (i-1)*dp(i-1, j-1)\]

如果你对组合数学足够熟悉,就会发现上面那个式子其实是第一类斯特林数 \(s(i, j)\) 的递推式。

考虑其组合意义。如果我们最后符合要求的数为 \(a_{p_1}, a_{p_2}, \dots , a_{p_j}\),则一定有 \(a_{p_1+1\dots p_2-1} < a_{p_1}\)。

如果我们把 \(a_{p_1...p_2-1}\) 看成一个整体,则这个整体对答案的贡献其实是圆排列——每个排列都必须要保证 \(a_{p_1}\) 在第一个位置,就像是某个圆排列将 \(a_{p_1}\) 旋转到第一个位置。

如果我们确定了数放在哪一个圆排列中,则圆排列之间的相对位置是唯一的,因为我们必须要满足 \(a_{p_1} < a_{p_2} < \dots < a_{p_j}\)。也就是说最后的方案数就是将 i 个数分成 j 个圆排列的方案数——即第一类斯特林数的定义。

既然扯到了组合意义,那么最初那个枚举最大值的位置可不可以直接用组合数学的方法来搞定了?

我们可以这样理解:先将 n-1(除最大值以外)个数分成 a+b-2 个圆排列,再将这 a+b-2 个圆排列黑白染色,选择 a-1 个染黑色(放在最大值左边),剩下的染白色(放在最大值右边)。则:

\[ans = s(n-1, a+b-2)*C(a+b-2,a-1)\]

@part - [email protected]

【接下来只是来讲讲怎么 O(nlogn) 求解第一类斯特林数的,如果你已经很熟悉了可以直接跳过这一段】

我们根据这样一个公式进行求解:

\[x(x+1)(x+2)\dots(x+n-1)=\sum_{i=0}^{n}s(n,i)x^i\]

有些类似于二项式定理。可以根据对最后一项是选择 x 还是 n-1 得到和我们递推公式一样的结果。

我们利用倍增解决这一问题。

记 \(f_n(x)=\prod_{i=0}^{n-1}(x+i) = a_0+a_1x+\dots+a_{n-1}x^{n-1}\)。

则 \(f_{2n}(x) = f(x)*f(x+n)\),\(f_{2n+1}(x)=f(x)*f(x+n)*(x+2n)\)。

如果已知 \(f(x+n)\),则可以用 fft 快速计算多项式乘法。

考虑怎么已知 \(f_n(x)\) 求 \(f_n(x+n)\)。将 \(f_n(x+n)\) 的式子写出来:

\[f_n(x+n)=\sum_{i=0}^{n-1}a_i(x+n)^i\]

二项式展开:

\[f_n(x+n)=\sum_{i=0}^{n-1}a_i(\sum_{j=0}^{i}C(i,j)*n^j*x^{i-j})\]

把内层的求和去掉:

\[f_n(x+n)=\sum_{0\le j\le i<n}a_i*C(i,j)*n^j*x^{i-j}\]

把组合数拆成阶乘形式,并适当整理:

\[f_n(x+n)=\sum_{0\le j\le i<n}(a_i*i!)*(\frac{n^j}{j!})*(\frac{x^{i-j}}{(i-j)!})\]

如果记 \(A_i = a_i*i!\),\(B_i = \frac{n^j}{j!}\),则我们相当于是要求解 A 与 B 的减法卷积。将 A 翻转一下就可以正常用 fft 做加法卷积,然后把结果再翻转回来即可。

@accepted [email protected]

注意一些该特判的地方还是要特判。

#include<cstdio>
#include<algorithm>
using namespace std;
const int G = 3;
const int MOD = 998244353;
const int MAXN = 400000;
int pow_mod(int b, int p) {
    int ret = 1;
    while( p ) {
        if( p & 1 ) ret = 1LL*ret*b%MOD;
        b = 1LL*b*b%MOD;
        p >>= 1;
    }
    return ret;
}
int fct[MAXN + 5], inv[MAXN + 5];
void ntt(int *A, int n, int type) {
    for(int i=0,j=0;i<n;i++) {
        if( i < j ) swap(A[i], A[j]);
        for(int l=(n>>1);(j^=l)<l;l>>=1);
    }
    for(int s=2;s<=n;s<<=1) {
        int t = (s>>1);
        int u = (type == 1) ? pow_mod(G, (MOD-1)/s) : pow_mod(G, (MOD-1)-(MOD-1)/s);
        for(int i=0;i<n;i+=s) {
            int p = 1;
            for(int j=0;j<t;j++,p=1LL*p*u%MOD) {
                int x = A[i+j], y = 1LL*A[i+j+t]*p%MOD;
                A[i+j] = (x + y)%MOD, A[i+j+t] = (x + MOD - y)%MOD;
            }
        }
    }
    if( type == -1 ) {
        int k = 1LL*fct[n-1]*inv[n]%MOD;
        for(int i=0;i<n;i++)
            A[i] = 1LL*A[i]*k%MOD;
    }
}
void init() {
    fct[0] = 1;
    for(int i=1;i<=MAXN;i++)
        fct[i] = 1LL*fct[i-1]*i%MOD;
    inv[MAXN] = pow_mod(fct[MAXN], MOD - 2);
    for(int i=MAXN-1;i>=0;i--)
        inv[i] = 1LL*inv[i+1]*(i+1)%MOD;
}
int comb(int n, int m) {
    return 1LL*fct[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int tmp1[MAXN + 5], tmp2[MAXN + 5], tmp3[MAXN + 5];
void sterling1(int *A, int n) {
    if( !n ) {
        A[0] = 1;
        return ;
    }
    int m = n/2, pw = 1, len;
    sterling1(A, m);
    for(len = 1;len <= n;len <<= 1);
    for(int i=0;i<=m;i++) tmp1[m - i] = 1LL*fct[i]*A[i]%MOD;
    for(int i=0;i<=m;i++) tmp2[i] = 1LL*inv[i]*pw%MOD, pw=1LL*pw*m%MOD;
    for(int i=m+1;i<len;i++) tmp1[i] = tmp2[i] = 0;
    ntt(tmp1, len, 1), ntt(tmp2, len, 1);
    for(int i=0;i<len;i++) tmp1[i] = 1LL*tmp1[i]*tmp2[i]%MOD;
    ntt(tmp1, len, -1);
    for(int i=0;i<=m;i++) tmp3[m - i] = 1LL*tmp1[i]*inv[m - i]%MOD;
    for(int i=0;i<=m;i++) tmp1[i] = A[i];
    for(int i=m+1;i<len;i++) tmp1[i] = tmp3[i] = 0;
    if( n & 1 ) {
        tmp2[1] = 1, tmp2[0] = (MOD + n - 1);
        for(int i=2;i<len;i++) tmp2[i] = 0;
    }
    else {
        tmp2[0] = 1;
        for(int i=1;i<len;i++) tmp2[i] = 0;
    }
    ntt(tmp1, len, 1), ntt(tmp2, len, 1), ntt(tmp3, len, 1);
    for(int i=0;i<len;i++) tmp1[i] = 1LL*tmp1[i]*tmp2[i]%MOD*tmp3[i]%MOD;
    ntt(tmp1, len, -1);
    for(int i=0;i<=n;i++) A[i] = tmp1[i];
}
int f[MAXN + 5];
int main() {
    int n, a, b; init();
    scanf("%d%d%d", &n, &a, &b);
    if( a + b > n + 1 || a == 0 || b == 0 ) {
        printf("%d\n", 0);
        return 0;
    }
    sterling1(f, n - 1);
/*
    for(int i=0;i<=n-1;i++)
        printf("%d ", f[i]);
    puts("");
*/
    printf("%lld\n", 1LL*f[a + b - 2]*comb(a + b - 2, a - 1)%MOD);
}

@[email protected]

写程序的时候突然发现斯特林数的简写是 STL。

我就说用998244353这个模数肯定是ntt嘛。

不要忘记乘上 \(\frac{1}{(i-j)!}\)。

奇数长度的还要多乘一个多项式。

边界当 n = 0 的时候,返回一个常数 1。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10259017.html

时间: 2024-10-30 13:25:38

@codeforces - [email protected] Bandit Blues的相关文章

@codeforces - [email&#160;protected] Mashmokh&#39;s Designed Problem

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,每个点的儿子是有序的. 现给定 m 次操作,每次操作是下列三种中的一种: (1)给定 u, v,询问 u, v 之间的距离. (2)给定 v, h,断开 v 到父亲的边,将 v 这棵子树加入到它的第 h 个祖先的最后一个儿子. (3)给定 k,询问在当前这棵树上

@codeforces - [email&#160;protected] T-Shirts

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 件 T-shirt,第 i 件 T-shirt 有一个 ci 和 qi,分别表示费用与质量. 同时有 k 个顾客,第 j 个顾客准备了 bj 的金钱去购买 T-shirt. 每个顾客的购买策略是相同的: 他会买他的资金范围内 q 值最大的一件,如果有多个选 c 最小的一件,每种

@codeforces - [email&#160;protected] Oleg and chess

目录 @description - [email protected] @[email protected] @part - [email protected] @part - [email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @description - [email protected] 给定一个 n*n 的棋

@codeforces - [email&#160;protected] Lucky Tickets

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 已知一个数(允许前导零)有 n 位(n 为偶数),并知道组成这个数的数字集合(并不一定要把集合内的数用完).求有多少种可能,使得这个数前半部分的数位和等于后半部分的数位和. 模 998244353. input 第一行两个整数:n k.表示这个数的位数以及组成这个数的数字集合大小.2

@codeforces - [email&#160;protected] Vus the Cossack and a Field

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的 01 矩阵,通过这个矩阵生成一个无穷矩阵,具体操作如下: (1)将这个矩阵写在左上角. (2)将这个矩阵每位取反写在右上角. (3)将这个矩阵每位取反写在左下角. (4)将这个矩阵写在右下角. (5)将得到的矩阵再作为初始矩阵,重复这些操作. 比如对于初始矩阵:

@codeforces - [email&#160;protected] Big Problems for Organizers

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个点连成一棵树,经过每条边需要花费 1 个单位时间. 现给出 m 次询问,每次询问给出两个点,需要求所有点同时出发,最终所有点到达这两个点之一的最小花费时间. input 第一行包含一个整数 n (2?≤?n?≤?100000) ,表示点数. 接下来 n-1 行每行两个 1~n 的

@codeforces - [email&#160;protected] Strongly Connected Tournament

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个选手参加了一场竞赛,这场竞赛的规则如下: 1.一开始,所有选手两两之间独立进行比赛(没有平局). 2.主办方将胜者向败者连边形成 n 个点的竞赛图. 3.主办方对这个竞赛图进行强连通分量缩点. 4.每一个强连通分量内部的选手重复步骤 1~3,直到每一个强连通分量内只剩一个选手.

@codeforces - [email&#160;protected] Rotate Columns (hard version)

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的矩阵 A. 定义一次操作为将矩阵的某一列竖着循环移位,你可以对任意列做任意次操作. 定义 ri 为第 i 行的最大值,最大化 r1 + r2 + ... + rn. Input 第一行一个整数 t (1≤t≤40),表示数据组数. 每组数据第一行包含两个整数 n m

@codeforces - [email&#160;protected] Koala and Notebook

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n 点 m 边的无向连通图,每条边的编号按照输入顺序依次为 1, 2, ..., m. 现从 1 号点出发,当经过编号为 i 的边时,将 i 写下来.因为写的数之间没有空隙,所以写下来的所有数最终会连成一个数. 对于每一个除 1 以外的点,当它作为终点时,最终连成的数最小是多