专栏文章

题解:P14352 排序

P14352题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@ming6gf4
此快照首次捕获于
2025/12/02 01:53
3 个月前
此快照最后确认于
2025/12/02 01:53
3 个月前
查看原文

题目大意

给定一个长度为 nn 的排列 aa,定义排序算法 A(k)A(k) 如下:
  • 执行 kk 轮操作,每轮从 i=1i=1n1n-1 依次检查,如果 ai>ai+1a_i > a_{i+1} 则交换它们。
问有多少个排列在执行 A(k)A(k) 后会变成完全升序的,答案对 998244353998244353 取模。

思路

冒泡排序性质

冒泡排序有一个重要性质:
  • 第 1 轮结束后,最大的元素会移动到最后一个位置。
  • 第 2 轮结束后,第二大的元素会移动到倒数第二个位置。
  • ...
  • tt 轮结束后,前 tt 大的元素都在它们的最终位置上。
因此,执行 kk 轮冒泡排序后,最大的 kk 个元素(即 n,n1,,nk+1n, n-1, \dots, n-k+1)已经在它们的正确位置上。

转化

一个排列可以唯一地由其逆序表 (e1,e2,,en)(e_1, e_2, \dots, e_n) 表示,其中 eie_i 表示在 aia_i 右边且比 aia_i 小的元素个数。
冒泡排序每轮每个元素最多左移一次,因此:
  • 如果某个元素需要左移 dd 次才能到达最终位置,那么至少需要 dd 轮排序。
  • 换句话说,如果 ei>ke_i > k,那么 kk 轮后这个元素还没到达最终位置。
所以,排列在 kk 轮冒泡排序后完全有序的充要条件是:对于所有 iieike_i \leq k

公式推导

由于 eie_i 的取值范围是 00nin-i,加上限制 eike_i \leq k 后,eie_i 的取值范围是 00min(k,ni)\min(k, n-i)
m=min(k,n1)m = \min(k, n-1),我们可以分两种情况:
  1. knk \geq n
    • 所有排列都能在 n1n-1 轮内排好序。
    • 答案就是 n!n!
  2. k<nk < n
    • 对于 inki \leq n-knikn-i \geq k,所以 eie_ik+1k+1 种取值。
    • 对于 i>nki > n-kni<kn-i < k,所以 eie_ini+1n-i+1 种取值。
    • 总方案数为: ans=(k+1)nk×j=1k(j+1)=(k+1)nk×k!\text{ans} = (k+1)^{n-k} \times \prod_{j=1}^k (j+1) = (k+1)^{n-k} \times k!

最终公式

ans={n!if kn(k+1)nk×k!mod998244353if k<n\text{ans} = \begin{cases} n! & \text{if } k \geq n \\ (k+1)^{n-k} \times k! \bmod 998244353 & \text{if } k < n \end{cases}

算法实现

  • 如果 knk \geq n,直接计算 n!mod998244353n! \bmod 998244353(注意 nn 不会太大,因为 k2×107k \leq 2\times 10^7)。
  • 如果 k<nk < n,计算 k!mod998244353k! \bmod 998244353(k+1)nkmod998244353(k+1)^{n-k} \bmod 998244353(用快速幂)。
  • 注意取模运算。

复杂度分析

  • 计算阶乘:O(k)O(k)
  • 快速幂:O(log(nk))O(\log(n-k))
  • 总复杂度:O(k+logn)O(k + \log n),可以处理 nn 高达 101810^{18} 的情况。

代码

CPP
#include <bits/stdc++.h>
using namespace std;

const int MOD = 998244353;

// 计算 a^b % MOD
long long qpow(long long a, long long b) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int main() {
    long long n, k;
    cin >> n >> k;
    if (k >= n) {
        // k >= n 时,所有排列都能排好序。
        long long ans = 1;
        for (int i = 1; i <= n; i++) {
            ans = ans * i % MOD;
        }
        cout << ans << endl;
    } else {
        // k < n 时,答案为 (k+1)^(n-k) * k!。
        long long fact = 1;
        for (int i = 1; i <= k; i++) {
            fact = fact * i % MOD;
        }
        long long ans = fact * qpow(k + 1, n - k) % MOD;
        cout << ans << endl;
    }

    return 0; // 好习惯。
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...