CF960G Bandit Blues 分治+NTT(第一类斯特林数)

$ \color{#0066ff}{ 题目描述 }$

给你三个正整数 \(n\)\(a\)\(b\),定义 \(A\) 为一个排列中是前缀最大值的数的个数,定义 \(B\) 为一个排列中是后缀最大值的数的个数,求长度为 \(n\) 的排列中满足 \(A = a\)\(B = b\) 的排列个数。\(n \le 10^5\),答案对 \(998244353\) 取模。

\(\color{#0066ff}{输入格式}\)

三个整数n,a,b

\(\color{#0066ff}{输出格式}\)

方案数

\(\color{#0066ff}{输入样例}\)

1 1 1

2 1 1

2 2 1

5 2 2

\(\color{#0066ff}{输出样例}\)

1
    
0
    
1
    
22

\(\color{#0066ff}{数据范围与提示}\)

\(N\) ( $1<=N<=10^{5} $ ), $A $and $ B$ ( $0<=A,B<=N $).

\(\color{#0066ff}{题解}\)

显然当\(a+b-1>n\)时,无解
考虑DP, \(f[i][j]\)表示i的排列有j个前缀最大值的方案数
考虑枚举1的位置\(f[i][j] = f[i-1][j-1]+(i-1)*f[i-1][j]\)
这是第一类斯特林数
实际上第一维滚动之后,可以发现, 就是把整个数组移动一位再加上自己的值*dp轮数
就相当于第i轮有i次操作,有1的方案取一个球,有i-1的方案一个球不取
于是构造生成函数\(\begin{aligned}\prod_{i=0}^{n-2}(x+i)\end{aligned}\)
本来应该是n-1的,实际上n的方案从1只转移n-1次, 所以-1
这个式子可以分治+NTT快速求出
最后还要组合一下,可以考虑每个产生贡献的值,a个和b个分别分成a-1和b-1段
最后再乘一个\(C_{a+b-2}^{a-1}\)即可
#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
const int maxn = 4e5 + 10;
const int mod = 998244353;
int len, r[maxn];
using std::vector;
LL ksm(LL x, LL y) {
    LL re = 1LL;
    while(y) {
        if(y & 1) re = re * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return re;
}
void FNTT(vector<int> &A, int flag) {
    A.resize(len);
    for(int i = 0; i < len; i++) if(i < r[i]) std::swap(A[i], A[r[i]]);
    for(int l = 1; l < len; l <<= 1) {
        int w0 = ksm(3, (mod - 1) / (l << 1));
        for(int i = 0; i < len; i += (l << 1)) {
            int w = 1, a0 = i, a1 = i + l;
            for(int k = 0; k < l; k++, a0++, a1++, w = 1LL * w0 * w % mod) {
                int tmp = 1LL * A[a1] * w % mod;
                A[a1] = ((A[a0] - tmp) % mod + mod) % mod;
                A[a0] = (A[a0] + tmp) % mod;
            }
        }
    }
    if(!(~flag)) {
        std::reverse(A.begin() + 1, A.end());
        int inv = ksm(len, mod - 2);
        for(int i = 0; i < len; i++) A[i] = 1LL * A[i] * inv % mod;
    }
}
vector<int> operator * (vector<int> A, vector<int> B) {
    int tot = A.size() + B.size() - 1;
    for(len = 1; len <= tot; len <<= 1);
    for(int i = 0; i < len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) * (len >> 1));
    FNTT(A, 1), FNTT(B, 1);
    std::vector<int> ans;
    for(int i = 0; i < len; i++) ans.push_back(1LL * A[i] * B[i] % mod);
    FNTT(ans, -1);
    ans.resize(tot);
    return ans;
}
int n, a, b;
std::vector<int> work(int l, int r) {
    vector<int> ans;
    if(l == r) {
        ans.resize(2, 0);
        ans[1] += 1, ans[0] += l;
        return ans;
    }
    int mid = (l + r) >> 1;
    return work(l, mid) * work(mid + 1, r);
}
LL C(int x, int y) {
    LL ans1 = 1, ans2 = 1;
    for(int i = y + 1; i <= x; i++) ans1 = 1LL * ans1 * i % mod;
    for(int i = 1; i <= x - y; i++) ans2 = 1LL * ans2 * i % mod;
    return 1LL * ans1 * ksm(ans2, mod - 2) % mod;
}
int main() {
    n = in(), a = in(), b = in();
    if(!a || !b || a + b - 1 > n) return puts("0"), 0;
    if(n == 1) return puts("1"), 0;
    std::vector<int> ans;
    ans = work(0, n - 2);
    printf("%lld\n", 1LL * ans[a + b - 2] * C(a + b - 2, a - 1) % mod);
    return 0;
}

转载于:https://www.cnblogs.com/olinr/p/10580873.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值