【题目链接】
【思路要点】
- 首先,当且仅当一个排列不含有长度为 3 3 3 的下降子序列,冒泡排序的交换次数取到下界。
- **证明:**一个排列不含有长度为 3 3 3 的下降子序列等价于不存在一个位置前面有更大的数并且后面有更小的数。由于这样的位置一定会被向前/向后各交换一次,因此含有长度为 3 3 3 的下降子序列的排列交换次数取不到下界;其余的排列可以被写成两个上升子序列,其中第一个上升子序列中的位置只会被向后交换,第二个上升子序列中的位置只会被向前交换,因此不含有长度为 3 3 3 的下降子序列的排列交换次数可以取到下界。
- 考虑如何对此类排列计数,记 d p i , j dp_{i,j} dpi,j 表示还有 i i i 个元素未被确定,当前确定的序列中最大值为 N − j N-j N−j 时,未被确定部分可行的方案数。转移有如下两种:
1 1 1 、选择一个大于 N − j N-j N−j 的数,方案数为 ∑ k = 0 j d p i − 1 , k \sum_{k=0}^{j}dp_{i-1,k} ∑k=0jdpi−1,k 。
2 2 2 、当 N − j ≠ i N-j\ne i N−j̸=i ,选择一个小于 N − j N-j N−j 的数,必须选取当前剩下的最小值,方案数为 d p i − 1 , j dp_{i-1,j} dpi−1,j 。- 由此,借助前缀和优化,我们得到了一种 O ( N 2 ) O(N^2) O(N2) 的做法。
- 我们发现 d p i , j dp_{i,j} dpi,j 有意义,当且仅当 i ≤ j i≤j i≤j ,并且由 d p i , j dp_{i,j} dpi,j 的转移, d p i , j dp_{i,j} dpi,j 的组合意义为从 ( i , j ) (i,j) (i,j) 只向左和下走到 ( 0 , 0 ) (0,0) (0,0) 且中途不越过 y = x y=x y=x 的方案数。
- 类似卡特兰数公式的推导,由容斥原理可得 d p i , j = ( i + j i ) − ( i + 1 i − 1 ) dp_{i,j}=\binom{i+j}{i}-\binom{i+1}{i-1} dpi,j=(ii+j)−(i−1i+1) 。
- 预处理阶乘和逆元即可。
- 时间复杂度 $ O(N) $ 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1.2e6 + 5; const int P = 998244353; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN], fac[MAXN], inv[MAXN]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } void udpate(int &x, int y) { x += P - y; if (x >= P) x -= P; } int power(int x, int y) { if (y == 0) return 1; int tmp = power(x, y / 2); if (y % 2 == 0) return 1ll * tmp * tmp % P; else return 1ll * tmp * tmp % P * x % P; } int getdp(int x, int y) { if (y == 0) return 1; int ans = 1ll * fac[x + y] * inv[x] % P * inv[y] % P; int mns = 1ll * fac[x + y] * inv[x + 1] % P * inv[y - 1] % P; return (ans - mns + P) % P; } bool valid() { int Max = 0, Nax = 0; for (int i = 1; i <= n; i++) if (a[i] >= Max) Max = a[i]; else { if (a[i] >= Nax) Nax = a[i]; else return false; } return true; } int main() { fac[0] = 1; for (int i = 1; i < MAXN; i++) fac[i] = 1ll * fac[i - 1] * i % P; inv[MAXN - 1] = power(fac[MAXN - 1], P - 2); for (int i = MAXN - 2; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1ll) % P; int T; read(T); while (T--) { read(n); for (int i = 1; i <= n; i++) read(a[i]); static bool used[MAXN]; memset(used, false, sizeof(used)); int ans = getdp(n, n), Max = 0, Nax = 1; for (int i = 1; i <= n - 1; i++) { while (used[Nax]) Nax++; if (Nax != i && Nax < a[i]) udpate(ans, getdp(n - i, n - Max)); for (int j = Max + 1; j < a[i]; j++) udpate(ans, getdp(n - i, n - j)); used[a[i]] = true; chkmax(Max, a[i]); if (a[i] != Nax && a[i] < Max) break; } writeln(ans - valid()); } return 0; }