給你一個長爲 \(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; }