1. 程式人生 > >【BZOJ5416】【NOI2018】氣泡排序(動態規劃)

【BZOJ5416】【NOI2018】氣泡排序(動態規劃)

【BZOJ5416】【NOI2018】氣泡排序(動態規劃)

題面

BZOJ
洛谷
UOJ

題解

考場推出了就是兩個上升子序列,並且最長下降子序列長度不超過\(2\)。。。然後大力暴力狀壓\(dp\)混了\(44\)分。。。這個結論並不是很難證明,考慮一下氣泡排序的過程就好了。
實際上\(O(n^2)\)並不是很難吧。。。
先不考慮字典序的問題,設\(f[i][j]\)表示長度為\(i\)\([1,i]\)的排列,並且第一個數為\(j\)的合法排列方案數。
考慮如何轉移,如果\(j=1\),那麼後面怎麼放都行,所以$\displaystyle f[i][1]=\sum_{j=1}^{i-1}f[i-1][j] $。注意一下後面的數所謂的排列我們理解為拋掉第一個數之後,剩下的位置重新離散後的值。
否則不以\(1\)

開頭,如果下一個數比\(j\)大,那麼是沒有問題的,這部分就是\(\displaystyle f[i][j]\rightarrow \sum_{k=j}^{i-1}f[i-1][k]\)
那麼如果下一個數比\(j\)小,如果下個數不是\(1\)的話,顯然當前\(j\)、下一個數、\(1\),就構成了一個不合法的狀態。
實際上想想就是,所有小於當前數的數,也就是\([1,j-1]\)在排列中一定是相對有序的,即強制順序排列。
那麼這樣子的方案數是什麼呢?\(f[i-1][j-1]\)
為啥呢?
如果\(j=2\),顯然後面接一個\(1\)是沒有任何問題的。
否則的話,我們把接在後面的這樣子一個合法的排列給弄出來,然後把\(j\)
接在開頭,顯然會出現\(j,j-1,1\)這個不合法的東西,那麼我們做個轉換,強制令\(j-1\)變成\(1\),然後剩下的\([1,j-2]\)這些數全部加一,不難發現這樣子\([1,j-1]\)這些數就被強制順序排列了。
這樣子我們就有轉移了:\(\displaystyle f[i][j]=\begin{cases}\sum_{k=1}^{i-1}f[i-1][k]&j=1\\\sum_{k=j-1}^{i-1}f[i-1][k]&j>1\end{cases}\)

好的,我們顯然可以\(O(n^2)\)的算出\(f\)陣列,考慮怎麼計算答案了。
顯然是前面一部分卡緊範圍,然後從某個特定的位置開始,大於了給定的字典序,然後後面就可以放飛自我了。
假裝我們緊緊卡住了\([1,i-1]\)

這些位置,然後從\(i\)位置開始放飛自我。
假設有在\(q[i..n]\)中有\(k\)個數要小於\(q[1..i]\)中的字首最大值。
這裡分情況討論貢獻,並且以下討論都是在前綴合法的情況下進行的。判斷當前是否合法只需要維護字首最大值、字首次大值以及字尾最小值,實時檢查是否合法即可。

顯然後面要填進去的數中,大於當前字首最大值的數是沒有任何影響的,而小於字首最小值的數則強制從小往大填。怎麼強制從小往大填呢?顯然任何一個以大於字首最大值開頭的填法中,這一段都被強制按照順序填,顯然開頭比字首最大值要大的時候,一定被強制按照順序填了,所以這一部分的貢獻是\(\displaystyle \sum_{j=k+1}^{n-i+1}f[n-i+1][j]\)
然而發現似乎並沒有以沒有以比字首最大值小的數中的最小值為開頭的貢獻。
的確沒有考慮,但是因為這裡要限制字典序,所以當前這裡填的數必須比字首最大值大後面才不會受到字典序的限制,所以這裡就沒有這一部分貢獻了。。。

這樣子就可以做到\(O(Tn^2)\)的複雜度。
複雜度的瓶頸在於求解\(f\)陣列以及其後綴和。
顯然不能在通過求解\(f\)再求解其後綴和了,這樣子效率太差。
\(\displaystyle s[n][m]=\sum_{i=m}^n f[n][i]\),即\(f\)的字尾和。
那麼有:
\[\begin{aligned} s[n][m]&=\sum_{i=m}^n f[n][i]\\ &=\sum_{i=m}^n \sum_{j=i-1}^{n-1} f[n-1][j]\\ &=\sum_{j=m-1}^{n-1} f[n-1][j]\sum_{i=m}^{j+1} 1\\ &=\sum_{j=m-1}^{n-1} f[n-1][j]+\sum_{j=m-1}^{n-1} f[n-1][j]\sum_{i=m+1}^{j+1} 1\\ &=s[n-1][m-1]+\sum_{i=m+1}^{n}\sum_{j=i-1}^{n-1}f[n-1][j]\\ &=s[n-1][m-1]+s[n-1][m+1] \end{aligned}\]
明擺著找組合意義,走\(n\)步,每次可以向上走一步或者向下走一步,最終停在\(m\)位置,且中途不能走到\(0\)下面去的方案數。
行,那不就是這題??
那麼推出來就是\(\displaystyle s[n][m]={2n-m\choose n-m}-{2n-m\choose n-m-1}\)
然後???
然後就做完了啊。。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 1200001
#define MOD 998244353
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int jc[MAX],jv[MAX],inv[MAX];
int n,q[MAX],mn[MAX],ans;
int c[MAX];
int lb(int x){return x&(-x);}
void add(int x,int w){while(x<=n)c[x]+=w,x+=lb(x);}
int getsum(int x){int s=0;while(x)s+=c[x],x-=lb(x);return s;}
int C(int n,int m){if(m>n)return 0;return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int S(int n,int m){if(m>n)return 0;return (C(n+n-m,n-m)+MOD-C(n+n-m,n-m-1))%MOD;}
int main()
{
    jc[0]=jv[0]=inv[0]=inv[1]=1;
    for(int i=1;i<MAX;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    for(int i=2;i<MAX;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=1;i<MAX;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
    int T=read();
    while(T--)
    {
        n=read();ans=0;if(!n){puts("0");continue;}
        for(int i=1;i<=n;++i)q[i]=read(),c[i]=0;
        mn[n]=q[n];for(int i=n-1;i;--i)mn[i]=min(mn[i+1],q[i]);
        for(int i=1;i<=n;++i)add(q[i],1);
        for(int i=1,mx=0,smx=0;i<=n;++i)
        {
            if(smx>mn[i])break;
            int k=getsum(max(mx,q[i]));add(q[i],-1);
            ans=(ans+S(n-i+1,k+1))%MOD;
            if(q[i]<mx)smx=max(smx,q[i]);mx=max(mx,q[i]);
        }
        printf("%d\n",ans);
    }
}