好可怕。
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;
}
标签:AGC012F,int,Median,删数,Prefix,MAXN,空隙,考虑,移动
From: https://www.cnblogs.com/apjifengc/p/17416237.html