1. 程式人生 > >NOI2018 luogu4769 氣泡排序 組合計數

NOI2018 luogu4769 氣泡排序 組合計數

題意

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

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

然後也沒沉下心去看題解

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

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

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

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

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

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

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

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

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

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

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

左邊的同理可證

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

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

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

dp[i][j]=dp[i1][j+1]+k=0jdp[i1][k]dp[i][j] = dp[i - 1][j + 1] + \sum_{k = 0}^j dp[i - 1][k]

邊界條件是d

p[0][0]=1dp[0][0] = 1

	dp[0][0] = 1;
	for(int i = 1; i <= n; ++ i) {
		int sum = 0;
		for(int j = 0; i + j <= n; ++ j) {
			(sum += dp[i - 1][j]) %= mod;
			(dp[i][j] += sum) %= mod;
			(dp[i][j] += dp[i - 1][j + 1]) %= mod;
		}
	}
	printf("%d\n", dp[n][0]);

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

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

1-1的數

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

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

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

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

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

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

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

這些方案的總數就是CatalanNumberCatalan\ Number

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

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

那麼我們每次列舉一個字首

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

就可以做了

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

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

這個東西可以聯絡CatalanNumberCatalan\ 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;
}