题意

给你一个长为 \(n\) 的排列 \(p\) ,问你有多少个等长的排列满足

  1. 字典序比 \(p\) 大 ;
  2. 它进行冒泡排序所需要交换的次数可以取到下界,也就是令第 \(i\) 个数为 \(a_i\) ,下界为 \(\displaystyle \sum_{i=1}^{n} |i - a_i|\) 。

题解

一道特别好的题,理解后做完是真的舒畅~

参考了 liuzhangfeiabc 大佬的博客

首先我们观察一下最后的序列有什么性质:

考试 打表 观察的:对于每个数来说,它后面所有小于它的数都是单调递增的。

然后问了问肖大佬,肖大佬说这不就等价于

整个序列最长下降子序列长度不超过 \(3\) ,或者说整个序列能划分成两个最长上升子序列。

这看上去很有道理,但并不是那么显然?

证明:

考虑整个交换次数取到下限,那么对于任意一个数都需要取到下界。

反证法:那么如果存在一个长度 \(\ge 3\) 的最长下降子序列的话,那么这个元素首先会被右边小于它的数动一次位置,然后自己需要折返一次才能换到原位,那么就多了次数,不满足条件。

这个性质有什么用呢?我们发现这个上升子序列与最大值是有关系的。

也就是说我们填到第 \(i\) 个位置,假设当前最大值为 \(j\) ,我们可以随意填一个 \(> j\) 的数。但如果要填 \(< j\) 的数,需要从小到大一个个填,并且归入一个上升子序列。

那么我们可以根据这个进行一个显然的 \(dp\) 。

我们令大于当前最大值的数为 非限制元素 ,小于当前的数为 限制元素

令 \(f_{i,j}\) 表示还剩余 \(i\) 个数没填,其中后 \(j\) 个是大于当前最大值的 非限制元素 的方案数。

转移就是枚举下一个位置填一个 限制元素 或某一个 非限制元素

如果填限制元素,非限制元素的数量不变;

否则假设填入从小到大第 \(k\) 个非限制元素,非限制元素的数量就会减少 \(k\) 个。

考虑逆推,那么显然有一个转移方程了:

\[f_{i,j} = \sum_{k=0}^{j} f_{i-1, j - k}
\]

边界有

\[f_{i, 0} = 1 \\
\]

我们可以把这个看成一个二维矩阵。

那么对于 \((i, j)\) 这个点就是上一行前 \(j\) 个数的和,也就等价于

\[f_{i,j} = f_{i - 1, j} + f_{i, j - 1}
\]

这个矩阵其中一部分如下(不难发现要满足 \(j \le i\) 才能有取值):

\[\begin{bmatrix}
1 & 0 &0 & 0 & 0 & 0 \\
1 & 1 & 0 & 0 & 0 & 0 \\
1 & 2 & 2 & 0 & 0 & 0 \\
1 & 3 & 5 & 5 & 0 & 0 \\
1 & 4 & 9 & 14 & 14 & 0 \\
1 & 5 & 14 & 28 & 42 & 42 \\
\end{bmatrix}
\]

对角线上的数就是卡特兰数,但对于其中任意一个数可以由如下组合数导出:

\[\binom {i + j - 1} {j} - \binom {i + j - 1}{j - 2}
\]

它对于 \((i, j)\) 这个点的实际意义为从 \((0, 0)\) 一直向下和向右走,对于每一步要满足向下走的步数不少于向右走的步数,且最后走到 \((i, j)\) 的方案数。

对于这个组合数实际的组合意义,我并不知道。。。(有知道大佬快来告诉我啊)

但我们可以证明这个组合数是正确的:

类似与数学归纳,我们进行二维归纳

\[\begin{align}
f_{i, j} &= f_{i,j-1}+ f_{i - 1, j} \\
&= (\binom {i + j - 2}{j - 1} + \binom {i + j - 2}{j}) - (\binom{i + j - 2}{j - 3} + \binom{i + j - 2}{j - 2}) \\
& = \binom {i + j - 1} {j} - \binom {i + j - 1}{j - 2}
\end{align}
\]

然后我们继续考虑它的限制。

对于字典序限制,我们可以这样考虑。

枚举最终得到的序列和原序列不同的第一位(前面的都相同)然后对于这个分开计数。

假设当前做到第 \(i\) 位,给定排列中的这一位为 \(p_i\) ,后面有 \(big\) 个数比他大,\(small\) 个数比它小。

且当前的 非限制元素 有 \(lim\) 个(也就是后面大于前面出现过的最大值的数的个数)。

首先需要把 \(lim\) 和 \(big\) 取个 \(min\) ,这个是我们当前非限制元素的下界。

如果 \(lim = 0\) 那就意味着最大的数已经被我们填入,后面所有数只能从小到大填入,但这并不能满足字典序比原序列大的情况,直接退出即可。

否则我们需要计算的就是

\[\sum_{j=0}^{lim - 1} f_{n - i, j} = f_{n - i + 1, lim - 1}
\]

也就是后面有 \(n - i\) 个数需要填入,我们对于当前这一位任意选取一个 \(> p_i\) 的数,剩余 \(0 \sim lim - 1\) 个非限制元素的情况的方案数。

然后我们需要继续考虑能否继续向后填,也就是当前填入的数 \(a_i = p_i\) 是否合法

  1. 如果当前 \(big\) 更新了 \(lim\) ,那么说明 \(a_i\) 本身是一个非限制元素(也就是当前的最大值),合法;
  2. 否则,如果 \(a_i\) 是填入的最小数,那么是合法的;
  3. 其他情况显然都是不合法的。

复杂度是 \(O(n \log n)\) 。

总结

对于一类 \(dp\) 我们考虑忽略它们的具体取值,只考虑他们所属的种类。

以及一些 \(dp\) 可以用组合数进行表达。

然后字典序计数考虑按位去做(似乎可以容斥?)

代码

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * fh;
} void File() {
freopen ("inverse.in", "r", stdin);
freopen ("inverse.out", "w", stdout);
} const int N = 2e6 + 1e3, Mod = 998244353;
int fac[N], ifac[N]; int n, p[N], maxsta; int fpm(int x, int power) {
int res = 1;
for (; power; power >>= 1, x = 1ll * x * x % Mod)
if (power & 1) res = 1ll * res * x % Mod;
return res;
} void Math_Init(int maxn) {
fac[0] = ifac[0] = 1;
For (i, 1, maxn)
fac[i] = 1ll * fac[i - 1] * i % Mod;
ifac[maxn] = fpm(fac[maxn], Mod - 2);
Fordown (i, maxn - 1, 1)
ifac[i] = 1ll * ifac[i + 1] * (i + 1) % Mod;
} inline int C(int n, int m) {
if (n < 0 || m < 0 || n < m) return false;
return 1ll * fac[n] * ifac[m] % Mod * ifac[n - m] % Mod;
} #define lowbit(x) (x & -x)
namespace Fenwick_Tree { int sumv[N]; void Init() { For (i, 1, n) sumv[i] = 0; } void Update(int pos, int uv) {
for (; pos <= n; pos += lowbit(pos))
sumv[pos] += uv;
} int Query(int pos) {
int res = 0;
for (; pos; pos -= lowbit(pos))
res += sumv[pos];
return res;
} } inline int f(int i, int j) {
if (j > i) return 0;
return (C(i + j - 1, j) - C(i + j - 1, j - 2) + Mod) % Mod;
} int main () { File();
int cases = read(); Math_Init(2e6); while (cases --) { Fenwick_Tree :: Init();
n = read();
For (i, 1, n)
Fenwick_Tree :: Update((p[i] = read()), 1); int lim = n, ans = 0;
For (i, 1, n) {
Fenwick_Tree :: Update(p[i], -1);
int small = Fenwick_Tree :: Query(p[i]), big = (n - i) - small; if (!big) break ;
bool flag = !chkmin(lim, big); (ans += f(n - i + 1, lim - 1)) %= Mod;
if (flag && small) break;
} printf ("%d\n", ans); } return 0;
}

LOJ #2719. 「NOI2018」冒泡排序(组合数 + 树状数组)的更多相关文章

  1. Loj #2719. 「NOI2018」冒泡排序

    Loj #2719. 「NOI2018」冒泡排序 题目描述 最近,小 S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序. 下面是对冒泡排 ...

  2. loj 2719 「NOI2018」冒泡排序 - 组合数学

    题目传送门 传送门 题目大意 (相信大家都知道) 显然要考虑一个排列$p$合法的充要条件. 考虑这样一个构造$p$的过程.设排列$p^{-1}_{i}$满足$p_{p^{-1}_i} = i$. 初始 ...

  3. LOJ 2719 「NOI2018」冒泡排序——模型转化

    题目:https://loj.ac/problem/2719 首先要发现合法的充要条件是 | LDS | <=2 ! 因为有没用的步数,说明一个元素先往左移.又往右移(不会先往右移再往左移,因为 ...

  4. LOJ2719 「NOI2018」冒泡排序

    「NOI2018」冒泡排序 题目描述 最近,小S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 1 到n 的排列的冒泡排序. 下面是对冒泡排序的算法描述. 输入:一个长度为n 的排列p[ ...

  5. LOJ #2721. 「NOI2018」屠龙勇士(set + exgcd)

    题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...

  6. loj#2718. 「NOI2018」归程

    题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...

  7. loj#2721. 「NOI2018」屠龙勇士

    题目链接 loj#2721. 「NOI2018」屠龙勇士 题解 首先可以列出线性方程组 方程组转化为在模p意义下的同余方程 因为不保证pp 互素,考虑扩展中国剩余定理合并 方程组是带系数的,我们要做的 ...

  8. BZOJ_5416_[Noi2018]冒泡排序_DP+组合数+树状数组

    BZOJ_5416_[Noi2018]冒泡排序_DP+组合数+树状数组 Description www.lydsy.com/JudgeOnline/upload/noi2018day1.pdf 好题. ...

  9. 「NOI2018」冒泡排序

    「NOI2018」冒泡排序 考虑冒泡排序中一个位置上的数向左移动的步数 \(Lstep\) 为左边比它大的数的个数,向右移动的步数 \(Rstep\) 为右边比它大的数的个数,如果 \(Lstep,R ...

随机推荐

  1. 使用ArcGIS Earth矢量化高精度的数据(kml转图层转shp/要素类)

    大家好,这次来分享干货.做地理分析的同学,或者需要使用地图却不知道哪里有精度较高矢量数据(如校园图)的时候,怎么办呢? 我们知道ArcGIS提供了精度较高的全球影像图,基于此,可以自己进行矢量化,然后 ...

  2. Android apk安装时出现“解析软件包错误”

    有时候在安装apk的时候会出现解析软件包出错 (Android studio)解决方法如下: 关闭Instant Run功能: File-Settings-...看下图: 将红色框内的勾取消. 如果还 ...

  3. Android 图片加载框架 Glide4.x

    概述 Glide是一个图片加载框架,使得我们可以轻松的加载和展示图片 Glide4.x新增apply()来进行设置,apply可以调用多次,但是如果两次apply存在冲突的设置,会以最后一次为准 新增 ...

  4. 自定义JDBC链接池

    上篇简单介绍了jdbc链接数据库: 本篇就说一下自定义连接池以及增删改查的测试: 自定义连接池 自定义链接池的原因 JDBC连接中用到Connection   在每次对数据进行增删查改 都要 开启  ...

  5. linux环境下快速安装Mariadb和Redis

    一 Mariadb(Mysql)篇 1.新建一个yum源仓库 touch /etc/yum.repos.d/Mariadb.repo 2.在这个yum源仓库文件中,添加仓库url地址 [mariadb ...

  6. docker面试整理

    为什么要使用docker  https://www.cnblogs.com/AshOfTime/p/10755479.html docker的使用场景 docker和虚拟机比较的优势   https: ...

  7. uiautomator2 使用Python测试 Android应用

    GitHub地址:https://github.com/openatx/uiautomator2 介绍 uiautomator2 是一个可以使用Python对Android设备进行UI自动化的库.其底 ...

  8. 在Fabric ChainCode中导入第三方包(以状态机为例)

    在企业级应用开发中,经常会涉及到流程和状态,而有限状态机(FSM)则是对应的一种简单实现,如果复杂化,就上升到Workflow和BPM了.我们在Fabric ChainCode的开发过程中,也很可能涉 ...

  9. 导出pdf功能

    本程序下载地址: PDF是我们极其常用的文件格式,但对如何生成PDF,个人一直觉得很神秘,其实利用一些公开的PDF库,我们就可以直接生成PDF文件,而不用关注PDF文件的内部细节.我知道的PDF库有如 ...

  10. 领域驱动设计系列文章(2)——浅析VO、DTO、DO、PO的概念、区别和用处

    本篇文章主要讨论一下我们经常会用到的一些对象:VO.DTO.DO和PO. 由于不同的项目和开发人员有不同的命名习惯,这里我首先对上述的概念进行一个简单描述,名字只是个标识,我们重点关注其概念: 概念: ...