P14534 [RMI 2018] W
考虑对于每种值从小到大去放,设 \(f_{i, s}\) 表示到了第 \(i\) 种数,\(5\) 个峰谷的选择情况(有没有被确定)的方案数,转移时枚举超集转移即可,需要注意一些特殊的边界情况以及 \(s\) 的合法性(不然可能会有重)。
如果朴素这么做绝对会重,你可以选择强制必须先选峰再选谷(这样比较好转移且本身去重),这样就比较好写了。
这种排列计数就是通过数分类插入排列的同时维护排列形态。
从别的地方找的代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 5, M = 1e6 + 5, P = 1000000007, inv2 = (P + 1) / 2;
int n, a[N], fac[N], inv[N], f[N][1 << 5], cnt[M];
set<int>s;
void init() {
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i < N; i++)
fac[i] = fac[i - 1] * i % P, inv[i] = (P - P / i) * inv[P % i] % P;
for (int i = 2; i < N; i++)
inv[i] = inv[i - 1] * inv[i] % P;
}
int C(int n, int m) {
if (!m)
return 1;
if (n < m)
return 0;
return fac[n] * inv[n - m] % P * inv[m] % P;
}
bool check(int s) {
if ((s >> 1 & 1) && (!(s & 1) || !(s >> 2 & 1)))
return 0;
if ((s >> 3 & 1) && (!(s >> 2 & 1) || !(s >> 4 & 1)))
return 0;
return 1;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
init();
for (int i = 1; i <= n; i++)
cin >> a[i], cnt[a[i]]++;
sort(a + 1, a + 1 + n);
int t = unique(a + 1, a + 1 + n) - a - 1;
vector<int>vt;
vt.push_back(0);
for (int i = t; i >= 1; i--)
vt.push_back(cnt[a[i]]);
f[0][0] = 1;
for (int i = 1; i <= t; i++) {
for (int s = 0; s < 1 << 5; s++) {
if (!check(s))
continue;
for (int t = s; t < 1 << 5; t = ((t + 1) | s)) {
if (!check(t))
continue;
if ((t >> 1 & 1) && (!(s & 1) || !(s >> 2 & 1)))
continue;
if ((t >> 3 & 1) && (!(s >> 2 & 1) || !(s >> 4 & 1)))
continue;
int ct = __builtin_popcount(s ^ t), rest = vt[i] - ct;
if (!(t >> 1 & 1) && (s >> 2 & 1))
ct++;
if (!(t >> 1 & 1) && (s & 1))
ct++;
if (!(t >> 3 & 1) && (s >> 2 & 1))
ct++;
if (!(t >> 3 & 1) && (s >> 4 & 1))
ct++;
f[i][t] = (f[i][t] + f[i - 1][s] * C(ct + rest - 1, ct - 1) % P) % P;
}
}
}
cout << f[vt.size() - 1][(1 << 5) - 1];
return 0;
}

浙公网安备 33010602011771号