【UOJ#394】[NOI2018] 冒泡排序
題目鏈接
題意
求有多少個字典序嚴格大於給定排列 \(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] 冒泡排序