ABC282G Similar Permutation【DP】
对于两个排列 \(A,B\),定义其相似度 \(f(A,B)\) 为满足 \(0 \leq i <n\) 且 \((a_{i+1} - a_{i})(b_{i+1} - b_i) > 0\) 的 \(i\) 的个数。
给定 \(n,m,P\),求 \(f(A,B) = k\) 的排列 \((A,B)\) 对数,答案对 \(P\) 取模。
\(2 \leq n \leq 100\),时限 \(\text{4.0s}\)。
\((a_{i+1} - a_i)(b_{i+1} - b_i) > 0\),就是说要么 \(a_{i} < a_{i+1}\) 且 \(b_i < b_{i+1}\),要么 \(a_i > a_{i+1}\) 且 \(b_i > b_{i+1}\)。
考虑这样生成排列对 \((A,B)\):从 \(1\) 到 \(n\) 枚举 \(i\),依次确定 \(a_i,b_i\),并且在此过程中,我们保证枚举到 \(i\) 时 \(a_j,b_j \leq i(j \leq i)\)。具体来说,假设当前确定了 \(a_i = p,b_i = q(p,q\leq i)\),那么我们找到所有 \(a_j \geq p(j < i)\) 的 \(j\),令 \(a_j \gets a_j + 1\)。对 \(b_i\) 同理。由归纳容易知道此时一定有 \(a_j,b_j \leq i(j \leq i)\)。
显然每个序列对 \((A,B)\) 唯一对应一种生成方式。这样做的好处是,每次确定 \(a_i,b_i\) 后并不会改变前面的大小关系,因此我们就只需要考虑 \(a_i\) 和 \(a_{i - 1}\)、\(b_i\) 和 \(b_{i-1}\) 的大小关系了。
这就很简单了。考虑 DP,设 \(f_{i,p,q,k}\) 为考虑了前 \(i\) 个位置,当前 \(a_i = p,b_i = q\) 且 \(f(A,B) = k\) 的方案数,根据排列生成的过程可得转移如下:
容易使用前缀和优化至 \(\mathcal{O}(n^4)\),可以通过本题。
枚举 \(a_i,b_i\) 在 \(1 \sim i\) 中的排名并不难想到,但关键在如何建立 \(i - 1 \to i\) 的映射使得状态始终合法。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
int n, m, P;
int f[N][N][N], g[N][N][N][4];
void add(int &x, int y) {
x = (x + y >= P) ? (x + y - P) : (x + y);
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> P;
for (int i = 1; i <= n; i++) {
if (i == 1) {
f[1][1][0] = 1;
} else {
for (int p = 1; p <= i; p++) {
for (int q = 1; q <= i; q++) {
for (int k = 0; k <= m; k++) {
if (k == 0) {
f[p][q][k] = (g[p - 1][q][k][2] + g[p][q - 1][k][3]) % P;
} else {
f[p][q][k] = (1LL * g[p - 1][q - 1][k - 1][0] + g[p][q][k - 1][1] + g[p - 1][q][k][2] + g[p][q - 1][k][3]) % P;
}
}
}
}
}
for (int k = 0; k <= m; k++) {
for (int p = 1; p <= i; p++) {
for (int q = 1; q <= i; q++) {
for (int o = 0; o < 4; o++) {
g[p][q][k][o] = f[p][q][k];
}
}
}
}
for (int k = 0; k <= m; k++) {
for (int p = 1; p <= i; p++) {
for (int q = 1; q <= i; q++) {
add(g[p][q][k][0], g[p - 1][q][k][0]);
}
}
for (int p = 1; p <= i; p++) {
for (int q = 1; q <= i; q++) {
add(g[p][q][k][0], g[p][q - 1][k][0]);
}
}
for (int p = i; p >= 1; p--) {
for (int q = i; q >= 1; q--) {
add(g[p][q][k][1], g[p + 1][q][k][1]);
}
}
for (int p = i; p >= 1; p--) {
for (int q = i; q >= 1; q--) {
add(g[p][q][k][1], g[p][q + 1][k][1]);
}
}
for (int p = 1; p <= i; p++) {
for (int q = i; q >= 1; q--) {
add(g[p][q][k][2], g[p - 1][q][k][2]);
}
}
for (int p = 1; p <= i; p++) {
for (int q = i; q >= 1; q--) {
add(g[p][q][k][2], g[p][q + 1][k][2]);
}
}
for (int p = i; p >= 1; p--) {
for (int q = 1; q <= i; q++) {
add(g[p][q][k][3], g[p + 1][q][k][3]);
}
}
for (int p = i; p >= 1; p--) {
for (int q = 1; q <= i; q++) {
add(g[p][q][k][3], g[p][q - 1][k][3]);
}
}
}
}
cout << g[n][n][m][0] << "\n";
return 0;
}