「解题报告」AGC012F Prefix Median

发布时间 2023-05-19 20:56:22作者: APJifengc

好可怕。

AtCoder 的一贯风格,先找合法序列的充要条件,然后 DP 计数。

首先把数组排序,这个显然。

先找显然的必要条件。首先 \(b_i \in [i,2n - i]\),这个比较显然。

然后发现加数很不好考虑,我们考虑倒过来删数。每次删两个数,不难发现中位数只会不变或向左 / 向右移动一位。于是,我们有另外一个必要条件,就是前面的 \(b_i\) 不能在后面的某两个相邻的 \(b_j, b_{j + 1}\) 之间,因为每次只能移动一位,说明之间的数一定已经被删除了,那么前面的数就不能在这个区间里了。

然后我们就可以证明这个东西是充分条件了。具体证明考虑归纳。还是每次删数,考虑第 \(i + 1\) 个中位数向左移动、向右移动还是不移动。如果是 \(i + 1\) 向左移动到 \(i\),那么我们一定可以在 \([i + 1, 2i + 1]\) 之间找到两个数满足没有在 \(b_1, b_2, \cdots, b_{i - 1}\) 中出现。同时为了满足 \(b_{i - 1}\)\(b_i\) 相差之多一格,我们一定优先把中间的至多两个空隙删除。由于第二个必要条件,中间的空隙一定没有出现过数,所以一定合法。向右移动同理。而不变的情况一定能在 \([1, i]\)\([i + 2, 2i + 1]\) 分别找到一个没出现过的数。同样的,为了满足相差最多一格,优先删空隙,空隙至多也只有一格。于是这样就证明了充分性。

然后考虑设计个 DP 统计这样的序列数。我们还是考虑删数的过程,考虑当前的这个数可以向左走到某个点,或向右走到某个点,走到这个点之后会将中间的所有点全部删除。那么我们记 \(f_{i, l, r}\) 表示考虑到第 \(i\) 个数,左边有 \(l\) 个点可以走,右边有 \(r\) 个点可以走的方案数,转移考虑不动,向左走若干步或向右走若干步,复杂度 \(O(n^4)\)。注意可能有重复的数,为了防止方案算重,同一个数我们只考虑一次出现,钦定其它位置的出现一定不选。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105, P = 1000000007;
int n, a[MAXN];
int f[MAXN][MAXN][MAXN];
void add(int &a, int b) {
    a += b;
    if (a >= P) a -= P;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= 2 * n - 1; i++) {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + 1 + 2 * n - 1);
    f[n][0][0] = 1;
    for (int i = n; i > 1; i--) {
        int dx = a[i] != a[i - 1], dy = a[2 * n - i] != a[2 * n - i + 1];
        for (int x = 0; x <= 2 * n - 1; x++) {
            for (int y = 0; y <= 2 * n - 1; y++) if (f[i][x][y]) {
                add(f[i - 1][x + dx][y + dy], f[i][x][y]);
                for (int k = 1; k <= x + dx; k++) 
                    add(f[i - 1][x + dx - k][y + dy + 1], f[i][x][y]);
                for (int k = 1; k <= y + dy; k++)
                    add(f[i - 1][x + dx + 1][y + dy - k], f[i][x][y]);
            }
        }
    }
    int ans = 0;
    for (int x = 0; x <= 2 * n - 1; x++) {
        for (int y = 0; y <= 2 * n - 1; y++) {
            add(ans, f[1][x][y]);
        }
    }
    printf("%d\n", ans);
    return 0;
}