NOI2018 luogu4769 冒泡排序 組合計數

題意

  • 定義好排列爲冒泡排序次數達到下界的排列,給你一個數nn,求有多少個字典序大於給定的排列qq的好排列pp

這個題擱在這裏好久沒去做

然後也沒沉下心去看題解

好排列一定滿足每次交換都讓兩個數離自己的位置更近

那麼我們可以證明好排列不存在大於等於33的下降子序列

如果存在的話設下降子序列前三個分別爲a,b,ca, b, c

那麼在aa回到自己位置的交換中bb會被換到前面去

cc回到自己位置的交換中bb會被換到後面去

那麼這兩次交換對bb就是毫無意義的 那麼就不滿足好排列性質

相應的如果不存在大於等於33的下降子序列就一定是好排列

對於一個逆序對 如果小的那個數一定會向左移

要使排列不是好排列,那麼它之後肯定會向右移

此時的條件是右邊又存在比它更小的數

那麼就存在了大於等於33的下降子序列

左邊的同理可證

那麼我們可以寫出一個O(n3)O(n^3)dpdp

dp[i][j]dp[i][j]表示確定了前ii個位置

沒選的數中有jj個比當前選的數的最大值小

dp[0][0] = 1;
for(int i = 0; i < n; ++ i) 
	for(int j = 0; j < n; ++ j) {
		if(j) (dp[i + 1][j - 1] += dp[i][j]) %= mod;
		for(int k = 0; i + 1 + j + k <= n; ++ k)
			(dp[i + 1][j + k] += dp[i][j]) %= mod;
	}

我們把dpdp的兩維放到二維平面上看

那麼每次橫座標增加11,縱座標加上一個大於等於1-1的數

並且不能跨過xx

最後要使橫座標爲nn,縱座標爲00

那麼一種方案就對應了一條從(0,0)(0, 0)(n,0)(n, 0)的路徑

這樣子不好處理,我們換成括號序列來考慮

每次添加一個右括號,可以在右括號前加任意個左括號

加一個右括號表示橫座標+1+1,縱座標1-1

加一個左括號表示縱座標+1+1

那麼所有的合法的長度爲2n2n的括號序列都對應着一種方案

這些方案的總數就是Catalan NumberCatalan\ Number

我們可以通過預處理組合數來快速回答一個括號序列的方案數

那麼我們現在就差考慮字典序的限制了

那麼我們每次枚舉一個前綴

把字典序大於當前枚舉前綴的所有排列算進去

相當於強制加上一個左括號

算的時候實際上就是計算xx個左括號和yy個右括號構成的合法方案數

答案就是C(x+y,x)C(x+y,y+1)C(x + y, x) - C(x + y, y + 1)

這個東西可以聯繫Catalan NumberCatalan\ Number的推導來理解

複雜度O(n)O(n)

Codes

#include<cstdio>
#include<bitset>

using namespace std;

const int N = 6e5 + 10;
const int mod = 998244353;

bitset<N> vis;

int fac[N << 1], ifac[N << 1], q[N];

int qpow(int a, int x) {
	int ret = 1;
	while(x) {
		if(x & 1) ret = 1ll * ret * a % mod;
		x >>= 1, a = 1ll * a * a % mod;
	}
	return ret;
}

void Math_Init(int n) {
	fac[0] = 1;
	for(int i = 1; i <= n; ++ i)
		fac[i] = 1ll * fac[i - 1] * i % mod;
	ifac[n] = qpow(fac[n], mod - 2);
	for(int i = n; i >= 1; -- i)
		ifac[i - 1] = 1ll * ifac[i] * i % mod;
}

int C(int n, int m) {
	return n < m ? 0 : 1ll * fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}

int cal(int x, int y) {
	return x > y ? 0 : (C(x + y, y) + mod - C(x + y, y + 1)) % mod;
}

int main() {
#ifdef ylsakioi
	freopen("4769.in", "r", stdin);
	freopen("4769.out", "w", stdout);
#endif

	Math_Init((N - 5) << 1);

	int t, n, ans = 0;

	for(scanf("%d", &t); t -- ; ans = 0, vis.reset()) {

		scanf("%d", &n);
		for(int i = 1; i <= n; ++ i)
			scanf("%d", &q[i]);

		int mn = 1, mx = 0, left = 0;

		for(int i = 1; i <= n; ++ i, -- left) {
			if(q[i] > mx) left += q[i] - mx, mx = q[i];
			(ans += cal(n - i - left, n - i + 1)) %= mod;
			if(mn < q[i] && q[i] ^ mx) break;
			for(vis[q[i]] = 1; vis[mn]; ++ mn) ;
		}

		printf("%d\n", ans);
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章