给你一个长为 \(n\) 的排列 \(p\) ,问你有多少个等长的排列知足c++
一道特别好的题,理解后作完是真的舒畅~git
参考了 liuzhangfeiabc 大佬的博客 。spa
首先咱们观察一下最后的序列有什么性质:debug
考试
打表观察的:对于每一个数来讲,它后面全部小于它的数都是单调递增的。code
而后问了问肖大佬,肖大佬说这不就等价于blog
整个序列最长降低子序列长度不超过 \(3\) ,或者说整个序列能划分红两个最长上升子序列。排序
这看上去颇有道理,但并非那么显然?get
证实:博客
考虑整个交换次数取到下限,那么对于任意一个数都须要取到下界。数学
反证法:那么若是存在一个长度 \(\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\) 是否合法
复杂度是 \(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; }