【BZOJ5416】【UOJ394】【NOI2018】冒泡排序

【题目链接】

【思路要点】

  • 首先,当且仅当一个排列不含有长度为 3 3 3 的下降子序列,冒泡排序的交换次数取到下界。
  • **证明:**一个排列不含有长度为 3 3 3 的下降子序列等价于不存在一个位置前面有更大的数并且后面有更小的数。由于这样的位置一定会被向前/向后各交换一次,因此含有长度为 3 3 3 的下降子序列的排列交换次数取不到下界;其余的排列可以被写成两个上升子序列,其中第一个上升子序列中的位置只会被向后交换,第二个上升子序列中的位置只会被向前交换,因此不含有长度为 3 3 3 的下降子序列的排列交换次数可以取到下界。
  • 考虑如何对此类排列计数,记 d p i , j dp_{i,j} dpi,j 表示还有 i i i 个元素未被确定,当前确定的序列中最大值为 N − j N-j Nj 时,未被确定部分可行的方案数。转移有如下两种:
    1 1 1 、选择一个大于 N − j N-j Nj 的数,方案数为 ∑ k = 0 j d p i − 1 , k \sum_{k=0}^{j}dp_{i-1,k} k=0jdpi1,k
    2 2 2 、当 N − j ≠ i N-j\ne i Nj̸=i ,选择一个小于 N − j N-j Nj 的数,必须选取当前剩下的最小值,方案数为 d p i − 1 , j dp_{i-1,j} dpi1,j
  • 由此,借助前缀和优化,我们得到了一种 O ( N 2 ) O(N^2) O(N2) 的做法。
  • 我们发现 d p i , j dp_{i,j} dpi,j 有意义,当且仅当 i ≤ j i≤j ij ,并且由 d p i , j dp_{i,j} dpi,j 的转移, d p i , j dp_{i,j} dpi,j 的组合意义为从 ( i , j ) (i,j) (i,j) 只向左和下走到 ( 0 , 0 ) (0,0) (0,0) 且中途不越过 y = x y=x y=x 的方案数。
  • 类似卡特兰数公式的推导,由容斥原理可得 d p i , j = ( i + j i ) − ( i + 1 i − 1 ) dp_{i,j}=\binom{i+j}{i}-\binom{i+1}{i-1} dpi,j=(ii+j)(i1i+1)
  • 预处理阶乘和逆元即可。
  • 时间复杂度 $ O(N) $ 。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1.2e6 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, a[MAXN], fac[MAXN], inv[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void udpate(int &x, int y) {
	x += P - y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int getdp(int x, int y) {
	if (y == 0) return 1;
	int ans = 1ll * fac[x + y] * inv[x] % P * inv[y] % P;
	int mns = 1ll * fac[x + y] * inv[x + 1] % P * inv[y - 1] % P;
	return (ans - mns + P) % P;
}
bool valid() {
	int Max = 0, Nax = 0;
	for (int i = 1; i <= n; i++)
		if (a[i] >= Max) Max = a[i];
		else {
			if (a[i] >= Nax) Nax = a[i];
			else return false;
		}
	return true;
}
int main() {
	fac[0] = 1;
	for (int i = 1; i < MAXN; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	inv[MAXN - 1] = power(fac[MAXN - 1], P - 2);
	for (int i = MAXN - 2; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
	int T; read(T);
	while (T--) {
		read(n);
		for (int i = 1; i <= n; i++)
			read(a[i]);
		static bool used[MAXN];
		memset(used, false, sizeof(used));
		int ans = getdp(n, n), Max = 0, Nax = 1;
		for (int i = 1; i <= n - 1; i++) {
			while (used[Nax]) Nax++;
			if (Nax != i && Nax < a[i]) udpate(ans, getdp(n - i, n - Max));
			for (int j = Max + 1; j < a[i]; j++)
				udpate(ans, getdp(n - i, n - j));
			used[a[i]] = true;
			chkmax(Max, a[i]);
			if (a[i] != Nax && a[i] < Max) break;
		}
		writeln(ans - valid());
	}
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值