NOI2018 luogu4769 氣泡排序 組合計數
阿新 • • 發佈:2018-12-18
題意
- 定義好排列為氣泡排序次數達到下界的排列,給你一個數,求有多少個字典序大於給定的排列的好排列
這個題擱在這裡好久沒去做
然後也沒沉下心去看題解
好排列一定滿足每次交換都讓兩個數離自己的位置更近
那麼我們可以證明好排列不存在大於等於的下降子序列
如果存在的話設下降子序列前三個分別為
那麼在回到自己位置的交換中會被換到前面去
在回到自己位置的交換中會被換到後面去
那麼這兩次交換對就是毫無意義的 那麼就不滿足好排列性質
相應的如果不存在大於等於的下降子序列就一定是好排列
對於一個逆序對 如果小的那個數一定會向左移
要使排列不是好排列,那麼它之後肯定會向右移
此時的條件是右邊又存在比它更小的數
那麼就存在了大於等於的下降子序列
左邊的同理可證
那麼我們可以寫出一個的
令表示確定了前個位置
沒選的數中有個比當前選的數的最大值小
則
邊界條件是
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]);
我們把的兩維放到二維平面上看
那麼每次橫座標增加,縱座標加上一個大於等於的數
最後要使橫座標為,縱座標為
那麼一種方案就對應了一條從到的路徑
這樣子不好處理,我們換成括號序列來考慮
每次新增一個右括號,可以在右括號前加任意個左括號
加一個右括號表示橫座標,縱座標
加一個左括號表示縱座標
那麼所有的合法的長度為的括號序列都對應著一種方案
這些方案的總數就是
我們可以通過預處理組合數來快速回答一個括號序列的方案數
那麼我們現在就差考慮字典序的限制了
那麼我們每次列舉一個字首
把字典序大於當前列舉字首的所有排列算進去
就可以做了
算的時候實際上就是計算個左括號和個右括號構成的合法方案數
答案就是
這個東西可以聯絡的推導來理解
複雜度
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;
}