[BZOJ5416][树状数组][组合数学]NOI2018:冒泡排序

BZOJ5416

分析:
我们先考虑为什么有些排列是好的,有些排列不好
可以发现交换次数的下界这个式子即表示每个位置的数到它原本该在的位置,且有个 1 / 2 1/2 1/2表示每次交换必须要让被交换的两个数都离自己的目标位置更近一步
所以我们如果把每个数到他的目的地看做一个箭头,则任意两个同向箭头不能有完全包含的关系
因为如果出现了包含的箭头,那大箭头一定会把小箭头往反向移动一步
所以我们在一个位置填的数只能是当前未使用的数的最小值或者比之前填过的所有数都大
d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前到第i个数,当前填的数在剩余的数中为第j大
那么可以写出转移 f [ i ] [ j ] = ∑ k = 0 i f [ i − 1 ] [ j − k ] f[i][j]=\sum_{k=0}^if[i-1][j-k] f[i][j]=k=0if[i1][jk]
前缀和优化 f [ i ] [ j ] = f [ i ] [ j − 1 ] + f [ i − 1 ] [ j ] f[i][j]=f[i][j-1]+f[i-1][j] f[i][j]=f[i][j1]+f[i1][j]
字典序应该不难解决吧
这就有80pts了
打表发现这是卡特兰数(我不会证
然后100pts就膜题解去了

Code(100pts):

#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
inline int dec(int x,int y){x-=y;if(x<0) x+=mod;return x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline int ksm(int a,int b){int res=1;for(;b;b>>=1,a=mul(a,a)) if(b&1) res=mul(res,a);return res;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
const int N=2e6;
int a[N],b[N],c[N],n;
int fac[N],ifac[N];
namespace bit{
	int tr[N];
	inline int lb(int x){return x&-x;}
	inline void add(int x){for(int i=x;i<=n;i+=lb(i)) ++tr[i];}
	inline int ask(int x){int res=0;for(int i=x;i;i-=lb(i)) res+=tr[i];return res;}
}
using namespace bit;
inline void init(int n){
	fac[0]=ifac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=mul(fac[i-1],i);
	ifac[n]=ksm(fac[n],mod-2);
	for(int i=n-1;i;i--) ifac[i]=mul(ifac[i+1],i+1);
}
inline int C(int n,int m){if(n<m) return 0;return mul(fac[n],mul(ifac[m],ifac[n-m]));}
inline int calc(int n,int m){
	if(!m) return 1;
	if(m==1) return n;
	return dec(C(n+m-1,m),C(n+m-1,m-2));
}
int main(){
	init(1500000);
	int t=read();
	while(t--){
		memset(tr,0,sizeof(tr));
		n=read();
		for(int i=1;i<=n;i++){
			a[i]=read();add(a[i]);
			c[i]=ask(a[i]-1);
			b[i]=n-a[i]-(i-1-c[i]);
		}
		int ans=0,cnt=n;
		for(int i=1;i<=n;i++){
			bool flag=(b[i]<cnt);
			if(flag) cnt=b[i];
			if(!cnt) break;
			inc(ans,calc(n-i+1,cnt-1));
			if(!flag && c[i]!=a[i]-1) break;
		}
		cout<<ans<<"\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值