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

题目链接

题意

求有多少个字典序严格大于给定排列 q i q_i qi 的排列满足其逆序对数(冒泡排序需要交换的次数)达到下限 1 2 ∑ i = 1 n ∣ i − p i ∣ \frac{1}{2}\sum_{i=1}^n |i-p_i| 21i=1nipi

Sol

很神仙的一题。

首先我们打表 (滑稽)
发现当没有字典序限制时的答案就是卡特兰数。
考虑感性理解,那么考虑卡特兰数的经典应用,它是最长下降子序列长度不超过 2 的排列的个数。
发现很有道理啊 owo。

于是我们就考虑在有字典序限制的条件下求解这个玩意。

dp有点难想到,我们设 f [ i ] [ j ] f[i][j] f[i][j] 表示已经填了的长度为 i i i,还没有加入的数中小于当前排列中最大值的数的个数是 j j j 的方案数。

为什么这样设状态? 因为我们发现最长下降子序列不能超过 2,在保证了已经加入的数合法的情况下,我们只需要关注最大值的情况就行了,小于最大值的数在后面都必须得升序排好,不然就会出现长度为 3 的下降序列。

转移的话,两种决策,要么填入一个小于当前最大值的数转移到 j − 1 j-1 j1 (注意这里一定是那个最小的数被填进去),或者是我们填入一个大于当前最大值的数,这样 j j j 势必不会减少,他的上限是 n − i n-i ni

初值是 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1 , 要求的东西是 f [ n ] [ 0 ] f[n][0] f[n][0]
那么这就像一个格路问题了。每次往右走一步的同时往上走任意步数或者往下一步(反正往上走多了也回不来了)
但是不能直接求,我们做一个转化。
容易发现往上和往下的步数是一样的,并且任意时刻不能走到 x 轴下方。
我们考虑用括号序列来解决这个问题。
每次就是加入任意数量左括号后加入一个右括号。
方案数用格路问题套路解决就是:
( 2 ∗ n − 1 n − 1 ) − ( 2 ∗ n − 1 n + 1 ) {2*n-1\choose n-1}-{2*n-1\choose n+1} (n12n1)(n+12n1)
(其实和卡特兰数的某个通项公式一样的啦)

这样我们得到一个复杂度为 O ( n 2 ) O(n^2) O(n2) 的做法。
每次按照数位dp 一样枚举在哪里字典序开始大于给定排列。
维护出这个时候选择恰好是 q i q_i qi 时应该有的左右括号个数。

加入左括号代表数变大,所以我们枚举再加入多少个左括号就能算答案了。

假设我们的左右括号数分别是 l , r l,r lr

贡献就是:
∑ i = l + 1 ( 2 ∗ n − i − r n − r ) − ( 2 ∗ n − i − r n − r + 1 ) \sum_{i=l+1} {2*n-i-r\choose n-r}-{2*n-i-r\choose n-r+1} i=l+1(nr2nir)(nr+12nir)

做一个代换令 x = n − i , y = n − r x=n-i,y=n-r x=ni,y=nr

∑ x = 0 n − l − 1 ( x + y y ) − ( x + y y + 1 ) \sum_{x=0}^{n-l-1}{x+y\choose y}-{x+y\choose y+1} x=0nl1(yx+y)(y+1x+y)

常见的玩意了:

∑ x = 0 n − l − 1 ( x + y y ) − ∑ x = 0 n − l − 1 ( x + y y + 1 ) \sum_{x=0}^{n-l-1}{x+y\choose y}-\sum_{x=0}^{n-l-1}{x+y\choose y+1} x=0nl1(yx+y)x=0nl1(y+1x+y)
( n − l + y y + 1 ) − ( n − l + y y + 2 ) {n-l+y\choose y+1}-{n-l+y\choose y+2} (y+1nl+y)(y+2nl+y)
( 2 ∗ n − l − r y + 1 ) − ( 2 ∗ n − l − r y + 2 ) {2*n-l-r\choose y+1}-{2*n-l-r\choose y+2} (y+12nlr)(y+22nlr)

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值