[题解] 2021-2022 ICPC, NERC, Northern Eurasia Onsite - F Fancy Stack
给你一个有 \(n\) 个数的单调不降的序列 \(a\),让这个序列重新排列为 \(b\)。使得对于所有偶数位有 \(b_2 < b_4 < ... < b_{n-2} < b_n\),对于所有奇数位 \(i\) 有 \(b_i < b_{i-1}, b_{i} < b_{i+1}\),注意是严格小于。
求合法 \(b\) 排列的方案数。对 \(998244353\) 取模,保证 \(n\) 是偶数。
赛时想了很长时间都不会,可能是我傻逼了。lzy 写了写 WA6,可惜最后没调出来。
下面的做法是翻译的题解。
先考虑所有数都不相同的情况。
设 \(f_{i,j}\) 表示考虑了最大的 \(i\) 个数,有 \(j\) 个数放在了偶数位上。
现在考虑第 \(i+1\) 个数。
- 如果放在偶数位上 \(f_{i+1,j+1} \gets f_{i,j}\)。
- 如果放在奇数位上 \(f_{i+1,j} \gets f_{i,j} \times (\max(j-1,0) + [j=\frac{n}{2}] - (i-j))\)。
当然这是不相同的情况。
如果存在数相同的话,可以把相同的数合并成一个,转移的时候枚举几个数作为偶数位,然后剩下的几个数放在奇数位上的方案数直接用组合数计算即可。 上面的这个是错的,因为要保证 \(b_{2} < b_4 < b_6 < ... < b_n\),所以相同的数中只能有 \(0\) 或 \(1\) 个数放在偶数位,不过算贡献的时候确实需要转化成组合数统计。
总复杂度 \(\mathcal O(n^2)\)
理论存在,开写!
现在是 15:11 我看我什么时候写完。
现在是 16:45 我写完了,调了 \(\infty\) 时间的缘故是多测没清干净 /px
#include<bits/stdc++.h>
#define LL long long
#define int long long
#define orz cout << "tyy YYDS!!!\n"
using namespace std;
const int MAXN = 5e3 + 10;
const int INF = 1e9 + 7;
const int mod = 998244353;
int n, m;
int fac[MAXN], inv[MAXN];
int a[MAXN], b[MAXN], cnt[MAXN], s[MAXN];
int f[MAXN][MAXN];
int read() {
int s = 0, f = 0; char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
void Init(int M) {
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for(int i = 2; i <= M; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
for(int i = 2; i <= M; ++i) inv[i] = inv[i - 1] * inv[i] % mod;
}
int C(int n, int m) { return (n < 0 || n < m) ? 0 : fac[n] * inv[m] % mod * inv[n - m] % mod; }
void Main() {
n = read(), m = 0;
for(int i = 0; i <= n + 1; ++i) a[i] = b[i] = cnt[i] = s[i] = 0;
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i) {
if(a[i] != a[i - 1]) b[++m] = a[i], cnt[m] = 1;
else cnt[m] ++;
}
for(int i = m; i >= 1; --i) s[i] = s[i + 1] + cnt[i];
for(int i = 0; i <= n + 1; ++i) for(int j = 0; j <= n + 1; ++j) f[i][j] = 0;
f[m + 1][0] = 1;
for(int i = m + 1, M = n / 2; i > 1; --i) { // 枚举已经填了哪一位,接下来要填的这一位是 i-1
for(int j = 0; j <= M; ++j) { // 枚举已经放了几个偶数位
for(int k = cnt[i - 1] - 1; k <= cnt[i - 1]; ++k) { // 枚举拿几个放在奇数位,奇数位应该放 (max(j-1,0)+(j==M)) 个,已经放了 (s[i] - j) 个
if(j + cnt[i - 1] - k <= M) {
f[i - 1][j + (cnt[i - 1] - k)] = (f[i - 1][j + (cnt[i - 1] - k)] + f[i][j] * C(max(j - 1, 0ll) + (j == M) - (s[i] - j), k) % mod) % mod;
}
}
}
}
printf("%lld\n", f[1][n/2]);
}
signed main() {
Init(5000);
int T = read();
while(T--) Main();
return 0;
}