1. 程式人生 > >【UOJ#394】[NOI2018] 冒泡排序

【UOJ#394】[NOI2018] 冒泡排序

pro 只需要 打表 ans include bit eve 應該 逆序

題目鏈接

題意

求有多少個字典序嚴格大於給定排列 \(q_i\) 的排列滿足其逆序對數(冒泡排序需要交換的次數)達到下限 \(\frac{1}{2}\sum_{i=1}^n |i-p_i|\)

Sol

很神仙的一題。

首先我們打表 (滑稽)
發現當沒有字典序限制時的答案就是卡特蘭數。
考慮感性理解,那麽考慮卡特蘭數的經典應用,它是最長下降子序列長度不超過 2 的排列的個數。
發現很有道理啊 owo。

於是我們就考慮在有字典序限制的條件下求解這個玩意。

dp有點難想到,我們設 \(f[i][j]\) 表示已經填了的長度為 \(i\),還沒有加入的數中小於當前排列中最大值的數的個數是 \(j\)

的方案數。

為什麽這樣設狀態? 因為我們發現最長下降子序列不能超過 2,在保證了已經加入的數合法的情況下,我們只需要關註最大值的情況就行了,小於最大值的數在後面都必須得升序排好,不然就會出現長度為 3 的下降序列。

轉移的話,兩種決策,要麽填入一個小於當前最大值的數轉移到 \(j-1\) (註意這裏一定是那個最小的數被填進去),或者是我們填入一個大於當前最大值的數,這樣 \(j\) 勢必不會減少,他的上限是 \(n-i\)

初值是 \(f[0][0]=1\) , 要求的東西是 \(f[n][0]\)
那麽這就像一個格路問題了。每次往右走一步的同時往上走任意步數或者往下一步(反正往上走多了也回不來了)

但是不能直接求,我們做一個轉化。
容易發現往上和往下的步數是一樣的,並且任意時刻不能走到 x 軸下方。
我們考慮用括號序列來解決這個問題。
每次就是加入任意數量左括號後加入一個右括號。
方案數用格路問題套路解決就是:
\[{2*n-1\choose n-1}-{2*n-1\choose n+1}\]
(其實和卡特蘭數的某個通項公式一樣的啦)

這樣我們得到一個復雜度為 \(O(n^2)\) 的做法。
每次按照數位dp 一樣枚舉在哪裏字典序開始大於給定排列。
維護出這個時候選擇恰好是 \(q_i\) 時應該有的左右括號個數。

加入左括號代表數變大,所以我們枚舉再加入多少個左括號就能算答案了。

假設我們的左右括號數分別是 \(l,r\)

貢獻就是:
\[\sum_{i=l+1} {2*n-i-r\choose n-r}-{2*n-i-r\choose n-r+1}\]

做一個代換令 \(x=n-i,y=n-r\)

\[\sum_{x=0}^{n-l-1}{x+y\choose y}-{x+y\choose y+1}\]

常見的玩意了:

\[\sum_{x=0}^{n-l-1}{x+y\choose y}-\sum_{x=0}^{n-l-1}{x+y\choose y+1}\]
\[{n-l+y\choose y+1}-{n-l+y\choose y+2}\]
\[{2*n-l-r\choose y+1}-{2*n-l-r\choose y+2}\]

code:

#include<bits/stdc++.h>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
const int mod=998244353;
template <typename T> inline void init(T&x){
    x=0;char ch=getchar();bool t=0;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    if(t) x=-x;return;
}
typedef long long ll;
template<typename T>inline void Inc(T&x,int y){x+=y;if(x>=mod) x-=mod;return;}
template<typename T>inline void Dec(T&x,int y){x-=y;if(x <  0) x+=mod;return;}
template<typename T>inline int fpow(int x,T k){int ret=1;for(;k;k>>=1,x=(ll)x*x%mod) if(k&1) ret=(ll)ret*x%mod;return ret;}
inline int Sum(int x,int y){x+=y;if(x>=mod) return x-mod;return x;}
inline int Dif(int x,int y){x-=y;if(x < 0 ) return x+mod;return x;}
const int N=6e5+10;
const int MAXN=2e6+1;
int P[N],n,num=0,Ct[MAXN];
int f[N],fac[MAXN],finv[MAXN],Inv[MAXN];
inline int C(int n,int m){return (n<m||m<0)? 0:((ll)fac[n]*finv[m]%mod*finv[n-m]%mod);}
inline void Sieve(const int n=MAXN-1){
    fac[0]=finv[0]=fac[1]=finv[1]=Inv[1]=1;
    for(int i=2;i<=n;++i) fac[i]=(ll)fac[i-1]*i%mod,Inv[i]=(ll)(mod-mod/i)*Inv[mod%i]%mod,finv[i]=(ll)finv[i-1]*Inv[i]%mod;
    Ct[0]=Ct[1]=1;
    for(int i=2;i<n;++i) Ct[i]=(ll)((i<<2)-2)*Ct[i-1]%mod*Inv[i+1]%mod;
    return;
}
inline int Cal(int l,int r){return Dif(C(2*n-l-r-1,n-r+1),C(2*n-l-r-1,n-r+2));}
bool vis[N];
#define lowbit(a) ((a)&(-a))
int tr[N];
inline void Update(int p){for(;p<=n;p+=lowbit(p))++tr[p];return;}
inline int  Query(int p){int ret=0;for(;p;p-=lowbit(p)) ret+=tr[p];return ret;}
inline int Solve(){
    Set(vis,0),Set(tr,0);
    int ans=0,mx=0;
    int x=0,y=0;int low=1;int l=0,r=0;  
    for(int i=1;i<=n;++i) {
        int les=P[i]-1-Query(P[i]);++x;
        if(P[i]>mx) {int d=les-y;y=les;l+=d+1;++r;}else --y,++r;
        const int D=n-r;
        Inc(ans,Dif(C(n-l+D,D+1),C(n-l+D,D+2)));
        vis[P[i]]=1;while(vis[low]) ++low;
        mx=max(P[i],mx);
        if(mx>P[i]&&P[i]>low) break;
        Update(P[i]);
    }
    return ans;
}
int main()
{
    Sieve();bool fl=1;
    int T;init(T);
    while(T--){init(n);
        for(int i=1;i<=n;++i) {init(P[i]);if(P[i]!=i) fl=0;}
        if(fl) printf("%d\n",Ct[n]-1);
        else printf("%d\n",Solve());
    }
    return 0;
}

【UOJ#394】[NOI2018] 冒泡排序