最近,小 S 對冒泡排序產生了濃厚的興趣。爲了問題簡單,小 S 只研究對 *\(1\) 到 \(n\) 的排列*的冒泡排序。c++
下面是對冒泡排序的算法描述。算法
輸入:一個長度爲 n 的排列 p[1...n]數組
輸出:p 排序後的結果。spa
for i = 1 to n docode
for j = 1 to n - 1 do排序
if(p[j] > p[j + 1])get
交換 p[j] 與 p[j + 1] 的值it
冒泡排序的交換次數被定義爲交換過程的執行次數。能夠證實交換次數的一個下界是 \(\frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert\),其中 \(p_i\) 是排列 \(p\) 中第 \(i\) 個位置的數字。若是你對證實感興趣,能夠看提示。class
小 S 開始專一於研究長度爲 \(n\) 的排列中,知足交換次數 \(= \frac 1 2 \sum_{i=1}^n \lvert i - p_i \rvert\) 的排列(在後文中,爲了方便,咱們把全部這樣的排列叫「好」的排列)。他進一步想,這樣的排列到底多很少?它們分佈的密不密集?原理
小 S 想要對於一個給定的長度爲 \(n\) 的排列 \(q\),計算字典序嚴格大於 \(q\) 的「好」的排列個數。可是他不會作,因而求助於你,但願你幫他解決這個問題,考慮到答案可能會很大,所以只需輸出答案對 \(998244353\) 取模的結果。
從文件 inverse.in
讀入數據。
輸入第一行包含一個正整數 \(T\),表示數據組數。
對於每組數據,第一行有一個正整數 \(n\),保證 \(n \leq 6 \times 10^5\)。
接下來一行會輸入 \(n\) 個正整數,對應於題目描述中的 \(q_i\),保證輸入的是一個 \(1\) 到 \(n\) 的排列。
輸出到文件 inverse.out
中。
輸出共 \(T\) 行,每行一個整數。
對於每組數據,輸出一個整數,表示字典序嚴格大於 \(q\) 的「好」的排列個數對 \(998244353\) 取模的結果。
\(n\leq 600000 ,\sum n\leq 2000000\)
能夠發現,若是一個序列合法,那麼對於任意一個數,它在冒泡排序的過程當中只能最多向一邊移動。咱們有知道,若是一個數的右邊有比它小的數,那麼它必定會向右移動,同理若是左邊有一個比它大的數,那麼也會向左移動。因此一個合法序列要求每一個數不能同時在兩邊都有逆序對。
根據這個原理就很容易獲得一個狀壓的作法了。
而後考慮有沒有多項式複雜度的作法。考慮從左往右填數。假設已經填了數中最大的是\(mx\),那麼尚未填的數中比\(mx\)小的那一部分已經有了左邊的逆序對,就不能有右邊的逆序對了,因此這部分數應該保持升序,而比\(mx\)大的那部分則沒有這個限制。
因而就能夠\(DP\)了。設\(f_{i,j}\)表示還剩\(i\)個數沒有填,沒有限制的數有\(j\)個的方案數。
初始化:\(f_{0,0}=1\)。
轉移:
\[ f_{i,j}=f_{i-1,j}+\sum_{k=0}^{j-1}f_{i-1,k}\\ =\sum_{k=0}^jf_{i-1,k} \]
考慮新加進來的數是否有限制,若是沒有,那麼知道他是第幾個沒有限制的數就能夠知道剩下的沒有限制的數還有多少個了。
這個\(DP\)方程是\(O(n^3)\)的,可是咱們也能夠將這個方程寫成如下形式:
\[ \begin{align} f_{i,j}&=f_{i,j-1}+f_{i-1,j}\\ f_{i,0}&=1 \end{align} \]
這樣就變成\(O(n^2)\)的了。
而後考慮這個\(f_{n,m}\)的組合意義:一個網格上,從\((0,0)\)走到\((n,m)\),每步只能向上或者向右走,而且\(i\geq j\)的方案數。也就是說整個過程不能通過\(y=x+1\)這條直線。
因而就能夠用一個叫折線定理的東西求解。求出\((0,0)\)對\(y=x+1\)的對稱點\((-1,1)\),而後求出由\((-1,1)\)走到\((n,m)\)的方案數。\((-1,1)\)到\((n,m)\)的每條路徑必定通過了\(y=x+1\)且惟一對應原問題的一個非法狀況(將路徑沿\(y=x+1\)翻折)。因此:
\[ f_{n,m}=\binom{n+m}{m}-\binom{n+m}{m-1} \]
考慮處理字典序的問題。咱們枚舉\(k\),表示\(k\)以前的全部位置都與\(q_i\)相同,而後在\(k\)這個位置填的數\(>q_k\)。假設\(q_{1\ldots k}\)中最大的是\(q_{mx}\),在\(q_{mx+1\ldots n}\)中比\(q_{mx}\)大的有\(cnt\)個,則此時答案爲\(f_{n-i+1,cnt-1}\)
咱們分狀況討論一下爲何是這樣的。咱們設\(k\)以前的最大值爲\(mx\)
當序列已經不合法了就結束。
具體實現能夠用樹狀數組。
代碼:
#include<bits/stdc++.h> #define ll long long #define N 600005 using namespace std; inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} const ll mod=998244353; ll ksm(ll t,ll x) { ll ans=1; for(;x;x>>=1,t=t*t%mod) if(x&1) ans=ans*t%mod; return ans; } int n; int q[N]; ll fac[N*2],ifac[N*2]; void pre(int n) { fac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; ifac[n]=ksm(fac[n],mod-2); for(int i=n-1;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod; } ll C(int n,int m) {return fac[n]*ifac[m]%mod*ifac[n-m]%mod;} ll cal(int n,int k) { if(k==0) return 1; if(k==1) return n; return (C(n+k,k)-C(n+k,k-1)+mod)%mod; } int low(int i) {return i&(-i);} int tem[N]; void add(int v,int f) {for(int i=v;i<=n;i+=low(i)) tem[i]+=f;} int query(int v) { int ans=0; for(int i=v;i;i-=low(i)) ans+=tem[i]; return ans; } bool vis[N]; int main() { pre(1200000); int T=Get(); while(T--) { ll ans=0; n=Get(); for(int i=1;i<=n;i++) tem[i]=0; for(int i=1;i<=n;i++) q[i]=Get(); int mx=0,cnt; for(int i=1;i<=n;i++) { if(q[i]>mx) { mx=q[i]; cnt=n-q[i]-(i-1-query(q[i]-1)); } (ans+=cal(n-i+1,cnt-1))%=mod; int low=query(q[i]-1); if(low!=i-1&&low!=q[i]-1) break; add(q[i],1); } cout<<ans<<"\n"; } return 0; }