P4769 [NOI2018] 冒泡排序 洛谷黑题题解附源码

[NOI2018] 冒泡排序

题目背景

请注意,题目中存在 n = 0 n=0 n=0 的数据。

题目描述

最近,小 S 对冒泡排序产生了浓厚的兴趣。为了问题简单,小 S 只研究对 1 1 1 n n n 的排列的冒泡排序。

下面是对冒泡排序的算法描述。

输入:一个长度为 n 的排列 p[1...n]
输出:p 排序后的结果。
for i = 1 to n do
	for j = 1 to n - 1 do
		if(p[j] > p[j + 1])
			交换 p[j] 与 p[j + 1] 的值

冒泡排序的交换次数被定义为交换过程的执行次数。可以证明交换次数的一个下界是 1 2 ∑ i = 1 n ∣ i − p i ∣ \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert 21i=1nipi,其中 p i p_i pi 是排列 p p p 中第 i i i 个位置的数字。如果你对证明感兴趣,可以看提示。

小 S 开始专注于研究长度为 n n n 的排列中,满足交换次数 = 1 2 ∑ i = 1 n ∣ i − p i ∣ = \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert =21i=1nipi 的排列(在后文中,为了方便,我们把所有这样的排列叫「好」的排列)。他进一步想,这样的排列到底多不多?它们分布的密不密集?

小 S 想要对于一个给定的长度为 n n n 的排列 q q q,计算字典序严格大于 q q q 的“好”的排列个数。但是他不会做,于是求助于你,希望你帮他解决这个问题,考虑到答案可能会很大,因此只需输出答案对 998244353 998244353 998244353 取模的结果。

输入格式

输入第一行包含一个正整数 T T T,表示数据组数。

对于每组数据,第一行有一个正整数 n n n,保证 n ≤ 6 × 1 0 5 n \leq 6 \times 10^5 n6×105

接下来一行会输入 n n n 个正整数,对应于题目描述中的 q i q_i qi,保证输入的是一个 1 1 1 n n n 的排列。

输出格式

输出共 T T T 行,每行一个整数。

对于每组数据,输出一个整数,表示字典序严格大于 q q q 的「好」的排列个数对 998244353 998244353 998244353 取模的结果。

样例 #1

样例输入 #1

1
3
1 3 2

样例输出 #1

3

样例 #2

样例输入 #2

1
4
1 4 2 3

样例输出 #2

9

提示

更多样例

更多样例请在附加文件中下载。

样例 3

见附加文件中的 inverse3.ininverse3.ans

样例 1 解释

字典序比 1   3   2 1 \ 3 \ 2 1 3 2 大的排列中,除了 3   2   1 3 \ 2 \ 1 3 2 1 以外都是「好」的排列,故答案为 3 3 3

数据范围

下面是对本题每个测试点的输入规模的说明。

对于所有数据,均满足 T = 5 T = 5 T=5(样例可能不满足)。

n m a x n_\mathrm{max} nmax 表示每组数据中 n n n 的最大值, ∑ n \sum n n 表示所有数据的 n n n 的和。

测试点 n m a x = n_\mathrm{max} = nmax= ∑ n ≤ \sum n \leq n 特殊性质
1 8 8 8 5   n m a x 5 \ n_\mathrm{max} 5 nmax
2 9 9 9 5   n m a x 5 \ n_\mathrm{max} 5 nmax
3 10 10 10 5   n m a x 5 \ n_\mathrm{max} 5 nmax
4 12 12 12 5   n m a x 5 \ n_\mathrm{max} 5 nmax
5 13 13 13 5   n m a x 5 \ n_\mathrm{max} 5 nmax
6 14 14 14 5   n m a x 5 \ n_\mathrm{max} 5 nmax
7 16 16 16 5   n m a x 5 \ n_\mathrm{max} 5 nmax
8 16 16 16 5   n m a x 5 \ n_\mathrm{max} 5 nmax
9 17 17 17 5   n m a x 5 \ n_\mathrm{max} 5 nmax
10 18 18 18 5   n m a x 5 \ n_\mathrm{max} 5 nmax
11 18 18 18 5   n m a x 5 \ n_\mathrm{max} 5 nmax
12 122 122 122 700 700 700 ∀ i q i = i \forall i \enspace q_i = i iqi=i
13 144 144 144 700 700 700
14 166 166 166 700 700 700
15 200 200 200 700 700 700
16 233 233 233 700 700 700
17 777 777 777 4000 4000 4000 ∀ i q i = i \forall i \enspace q_i = i iqi=i
18 888 888 888 4000 4000 4000
19 933 933 933 4000 4000 4000
20 1000 1000 1000 4000 4000 4000
21 266666 266666 266666 2000000 2000000 2000000 ∀ i q i = i \forall i \enspace q_i = i iqi=i
22 333333 333333 333333 2000000 2000000 2000000
23 444444 444444 444444 2000000 2000000 2000000
24 555555 555555 555555 2000000 2000000 2000000
25 600000 600000 600000 2000000 2000000 2000000

提示

下面是对交换次数下界是 1 2 ∑ i = 1 n ∣ i − p i ∣ \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert 21i=1nipi 的证明。

排序本质上就是数字的移动,因此排序的交换次数应当可以用数字移动的总距离来描述。对于第 i i i 个位置,假设在初始排列中,这个位置上的数字是 pi,那么我们需要将这个数字移动到第 p i p_i pi 个位置上,移动的距离是 ∣ i − p i ∣ \lvert i - p_i \rvert ipi。从而移动的总距离就是 ∑ i = 1 n ∣ i − p i ∣ \sum_{i=1}^n \lvert i - p_i \rvert i=1nipi,而冒泡排序每次会交换两个相邻的数字,每次交换可以使移动的总距离至多减少 2 2 2。因此 1 2 ∑ i = 1 n ∣ i − p i ∣ \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert 21i=1nipi 是冒泡排序的交换次数的下界。

并不是所有的排列都达到了下界,比如在 n = 3 n = 3 n=3 的时候,考虑排列 3   2   1 3 \ 2 \ 1 3 2 1,这个排列进行冒泡排序以后的交换次数是 3 3 3,但是 1 2 ∑ i = 1 n ∣ i − p i ∣ \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert 21i=1nipi 只有 2 2 2

感谢给我讲了这个题的神仙跳瓜 jumpmelon\textrm{jumpmelon}jumpmelon

首先看给的提示,我们可以发现在这样的排序方式下,对于每一个数都只向目标位置方向走,不换向。那么对于一个数 xxx,如果有一个比它大的数在它前面,那么它必须向左走;如果有一个比它小的数在它后面,那么它必须向右走。这样的排列是不合法的,即要求不存在长度超过 222 的下降子序列。

根据 Dilworth\textrm{Dilworth}Dilworth 定理,最长下降子序列的长度不超过 222,即整个排列最多被划分成 222 个上升子序列。

先不考虑字典序严格大于 qqq 的限制。

我们记 fi,jf_{i, j}fi,j 为前 iii 个的最大值为 jjj 后面 n−in - ini 个位置的方案数。我们考虑第 iii 个数填什么。注意 i⩽ji \leqslant jij

如果填比 jjj 大的数,那么一定可以接在 jjj 的后面;如果要填比 jjj 小的数,那么必须填当前还没有填的中最小的,否则上升子序列将不止 222 个,所以

fi,j={fi+1,k     (k>j)fi+1,j     =∑k=jnfi+1,k\begin{aligned} f_{i, j} &= \begin{cases} f_{i + 1, k} \ \ \ \ \ (k > j) \\ f_{i + 1, j} \ \ \ \ \ \end{cases} \\ &= \sum_{k = j}^n f_{i + 1, k} \end{aligned} fi,j={ fi+1,k     (k>j)fi+1,j     =k=jnfi+1,k

但是直接这样递推是 O(n2)O(n^2)O(n2) 的。我们把它以图像的形式表示,fi,jf_{i, j}fi,j 即表示从点 (i,j)(i, j)(i,j) 开始,每次向右走一步,向上走 x(x⩽0)x(x \leqslant 0)x(x0) 步,不与直线 y=x−1y = x - 1y=x1 (因为 i⩽ji \leqslant jij) 相交,走到点 (n,n)(n, n)(n,n) 的方案数。

如图,即从点 A(i,j)A(i, j)A(i,j) 走到 B(n,n)B(n, n)B(n,n) 的不与直线 y=x−1y = x - 1y=x1 相交的方案数。

首先,如果不考虑与直线不相交,即为走 n−in - ini 次,每次选择向上走 x(x⩾0)x (x \geqslant 0)x(x0) 步,一共走了 n−jn - jnj 步的方案数。模仿插板法,因为 xxx 可以取 000,我们把总个数加上划分数 n−in - ini,变成 n−i+n−jn - i + n - jni+nj 个物品划分成 n−in - ini 块的方案数,即 (2n−i−j−1n−i−1)\dbinom{2n - i - j - 1}{n-i-1}(ni12nij1)

再模仿 Catalan\textrm{Catalan}Catalan 数的推法,看第一个与直线 y=x−1y = x - 1y=x1 相交的位置。找点 (i,j)(i, j)(i,j) 关于直线 y=x−1y = x - 1y=x1 的对称点 (j+1,i−1)(j + 1, i - 1)(j+1,i1), 由于方案一一对应,所以,从点 (i,j)(i, j)(i,j) 出发,经过直线的方案数即为从点 (j+1,i−1)(j + 1, i - 1)(j+1,i1) 出发到点 (n,n)(n, n)(n,n) 的方案数。

得到

fi,j=calc(i,j)−calc(j+1,i−1)=(2n−i−j−1n−i−1)−(2n−i−j−1n−j−2)\begin{aligned} f_{i, j} &= calc(i, j) - calc(j + 1, i - 1) \\ &= \dbinom{2n - i - j - 1}{n - i - 1} - \dbinom{2n - i - j - 1}{n - j - 2} \\ \end{aligned} fi,j=calc(i,j)calc(j+1,i1)=(ni12nij1)(nj22nij1)

这样就得到 fffO(1)O(1)O(1) 求解啦!(然而 O(n2)O(n^2)O(n2) 有足足 808080 分,真香)

可以发现 f0,0f_{0, 0}f0,0 即为 Catalan\textrm{Catalan}Catalan 数,可以得到 121212 分。

回到有限制字典序严格大于 qqq 的原题上来。考虑一位一位枚举,假设当前枚举到第 iii 项,我们计数证前 i−1i - 1i1 项与 qqq 相同,第 iii 项大于 qiq_iqi 的排列个数。

mx=max⁡j=1i−1qjmx = \max_{j = 1}^{i - 1} q_jmx=maxj=1i1qjmnmnmn 为当前还没有用过的最小的数,v=qiv = q_iv=qi。第 iii 位只能填 mnmnmn 或大于 mxmxmx 的数。分类讨论

  • v=mnv = mnv=mn

    (因为 mnmnmn 是最小可以填的,所以 vvv 的下界是 mnmnmn。)

    此时,第 iii 项不能填 mnmnmn,只能大于 mxmxmx,故后面 n−in - ini 项的填法有 ∑k=mx+1nfi,k\sum_{k = mx + 1}^n f_{i, k}k=mx+1nfi,k 种(kkk 为第 iii 位填的数)。

  • mn<v<mxmn < v < mxmn<v<mx

    此时第 iii 位没有可以填的,后面不再存在合法方案。(但是还是要读完)

  • v⩾mxv \geqslant mxvmx

    此时第 iii 位可以填 mnmnmn 或大于 mxmxmx 的数,方案数为 ∑k=mxnfi,k\sum_{k = mx}^n f_{i, k}k=mxnfi,k

问题又来了,怎么求 fff 的前缀和呢?考虑 fff 的递推式 fi,j=∑k=jnfi+1,kf_{i, j} = \sum_{k = j}^n f_{i + 1, k}fi,j=k=jnfi+1,k,这正是一个前缀和的形式。所以

∑k=limnfi,k=fi−1,lim\sum_{k = lim}^n f_{i, k} = f_{i - 1, lim} k=limnfi,k=fi1,lim

不用像其他题解上说的要用树状数组,复杂度 O(Tn)O(Tn)O(Tn)(还好写)

完结撒花~

代码

注意数组要开 2n2n2n,写起来很简单,但是最开始由于没彻底搞懂想了半天

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

namespace TYC
{
   
    typedef long long ll;
    const int N = 1.2e6, p = 998244353;

    int fac[N + 5], inv[N + 5], vis[N + 5];

    inline int read()
    {
   
        int v = 0, fl = 0, ch = getchar();
        while (!isdigit(ch))
            fl |= (ch == '-'), ch = getchar();
        while (isdigit(ch))
            v = v * 10 + ch - '0', ch = getchar();
        return fl ? -v : v;
    }

    inline int qpow(int v, int tim)
    {
   
        int ans = 1;
        for (; tim; tim >>= 1, v = (ll)v * v % p)
            if (tim & 1)
                ans = (ll)ans * v % p;
        return ans;
    }

    void init()
    {
   
        fac[0] = 1;
        for (int i = 1; i <= N; i++)
            fac[i] = (ll)fac[i - 1] * i % p;
        inv[N] = qpow(fac[N], p - 2);
        for (int i = N; i; i--)
            inv[i - 1] = (ll)inv[i] * i % p;
    }

    inline int C(const int n, const int m)
    {
   
        return (n < 0 || m < 0 || n < m) ? 0 : int((ll)fac[n] * inv[m] % p * inv[n - m] % p);
    }

    int n;
    inline int F(const int i, const int j)
    {
   
        return i <= j && j <= n ? (C(2 * n - i - j - 1, n - i - 1) - C(2 * n - i - j - 1, n - j - 2) + p) % p : 0;
    }

    void work()
    {
   
        init();
        int T = read();
        while (T--)
        {
   
            n = read();
            memset(vis, 0, sizeof(int[n + 1]));
            int ans = 0, mx = 0, mn = 1, flag = 0, v;
            for (int i = 1; i <= n; i++)
            {
   
                v = read();
                if (flag)
                    continue;
                ans = (ans + (F(i - 1, max(mx, v) + 1) + p) % p) % p;
                if (mx > v && v > mn)
                    flag = 1;
                mx = max(mx, v);
                vis[v] = 1;
                while (vis[mn]) mn++;
            }
            printf("%d\n", ans);
        }
    }
}

int main()
{
   
    TYC::work();
    return 0;
}

相关推荐

  1. P5469 [NOI2019] 机器人 题解

    2024-01-27 00:42:03       41 阅读
  2. P4779 [模板] 单最短路径 题解 dijkstra算法

    2024-01-27 00:42:03       37 阅读
  3. P2179 [NOI2012] 骑行川藏

    2024-01-27 00:42:03       28 阅读
  4. P6974 [NEERC2015] Adjustment Office 题解

    2024-01-27 00:42:03       66 阅读
  5. 题解P6995 [NEERC2014] Knockout Racing

    2024-01-27 00:42:03       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-27 00:42:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-27 00:42:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-01-27 00:42:03       82 阅读
  4. Python语言-面向对象

    2024-01-27 00:42:03       91 阅读

热门阅读

  1. Python技术栈 —— 一种超时LRU的实现方式

    2024-01-27 00:42:03       64 阅读
  2. ModuleNotFoundError: No module named ‘half_json‘

    2024-01-27 00:42:03       62 阅读
  3. 工厂方法模式-C#实现

    2024-01-27 00:42:03       65 阅读
  4. C++核心编程:类和对象 笔记

    2024-01-27 00:42:03       46 阅读
  5. 2024美赛数学建模D题思路模型代码论文

    2024-01-27 00:42:03       60 阅读
  6. Python高超音速导弹

    2024-01-27 00:42:03       51 阅读