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\) 的方案数,根据排列生成的过程可得转移如下:

\[f_{i,p,q,k} = \sum_{p' < p,q'<q} f_{i-1,p',q',k - 1} + \sum_{p' < p,q'\geq q} f_{i-1,p',q',k} + \sum_{p' \geq p,q' < q} f_{i-1,p',q',k} + \sum_{p' \geq p,q'\geq q} f_{i-1,p',q',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;
}
posted @ 2022-12-21 00:36  came11ia  阅读(65)  评论(0编辑  收藏  举报