【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 轴下方。
我们考虑用括号序列来解决这个问题。
每次就是加入任意数量左括号后加入一个右括号。
方案数用格路问题套路解决就是:
(其实和卡特兰数的某个通项公式一样的啦)
这样我们得到一个复杂度为 \(O(n^2)\) 的做法。
每次按照数位dp 一样枚举在哪里字典序开始大于给定排列。
维护出这个时候选择恰好是 \(q_i\) 时应该有的左右括号个数。
加入左括号代表数变大,所以我们枚举再加入多少个左括号就能算答案了。
假设我们的左右括号数分别是 \(l,r\)
贡献就是:
做一个代换令 \(x=n-i,y=n-r\)
常见的玩意了:
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;
}