题意
你有一个长度为2n的数组,并且对于i=1~n都恰好有2个位置=i。现在进行如下操作n次:
对于所有位置
i
i
i满足
a
[
i
]
≠
0
a[i]\not=0
a[i]=0,若在此次操作前,i的右侧存在与a[i]相同的值,则
a
[
i
]
−
−
a[i]--
a[i]−−.
最后会剩下n个不为0的位置。现在告诉你这些位置,请求出一开始分布的方案数。
分析
有点东西的
- 先考察一下这个操作在干什么。操作结束之后,可以保证1~n都只有一个。
- 考虑位置2n,他的值始终不会变化。
- 那么其他位置最终不会为a[2n]。
- 考虑位置2n-1,若a[2n-1]=a[2n],则a[2n-1]最终为a[2n]-1;否则,其左边的位置就不能为a[2n-1]与a[2n].
- 以此类推,我们得到一个已知初始状态,求最终是什么的方法:从右往左做,若数x在右侧出现过,则x-=1,直到x=0,当前位置不会被留下来。
接下来对这个过程dp:
- 设 f [ i ] [ j ] f[i][j] f[i][j]表示,已经处理了 [ i , 2 n ] [i,2n] [i,2n],与1相连的覆盖块是 [ 1 , j ] [1,j] [1,j]
- 对于一个被留下来的位置:
假如他当前不能扩展第二维,那先暂时不填。
假如他能扩展第二维,那我们枚举其扩展了到j+k,那么还需要填上k-1个被留下来但是还没有填的位置,恰好将 [ j + 2 , j + k ] [j+2,j+k] [j+2,j+k]覆盖。这里的方案数需要预处理一下。 - 对于一个没有被留下来的位置,他可以填[1,j]里面任意一个数。由于不好分辨具体有多少种,不妨将两个相同的数标号,最后将答案除去2^n。这里方案数为 2 j − ( j + c n t [ 0 ] ) 2j-(j+cnt[0]) 2j−(j+cnt[0]),cnt[0]为i+1~2n中没有留下来的总数。
我们还要预处理 h [ i ] h[i] h[i]表示i个位置恰好放满1~i的方案数。设 g [ i ] [ j ] g[i][j] g[i][j]表示当前放了值 [ 1 , i ] [1,i] [1,i]的,每一种最多两个,共放了j个。只要保证值 [ 1 , i ] [1,i] [1,i]中的个数不超过i个,即能保证不会有多余的。最后使个数恰好为i个,即恰好完全覆盖,也就是 h [ i ] = g [ i ] [ i ] h[i]=g[i][i] h[i]=g[i][i]。 转移时要注意两个相同的数也要区分标号。
#include <bits/stdc++.h>
using namespace std;
const int N = 2 * 610, mo = 1e9 + 7;
typedef long long ll;
int n, a[N], key[N];
ll g[N][N], f[N][N];
ll jc[N], njc[N];
ll ksm(ll x, ll y) {
ll ret = 1;
for (; y; y >>= 1) {
if (y & 1)
ret = ret * x % mo;
x = x * x % mo;
}
return ret;
}
void getg() {
for (int i = 1; i <= n; i++) {
g[i - 1][0] = 1;
for (int j = 1; j <= i; j++) {
g[i][j] = (g[i - 1][j] + g[i - 1][j - 1] * j * 2) % mo;
if (j >= 2) {
g[i][j] = (g[i][j] + g[i - 1][j - 2] * j * (j - 1)) % mo;
}
}
}
}
ll C(ll n, ll m) {
if (n < m)
return 0;
return jc[n] * njc[n - m] % mo * njc[m] % mo;
}
int main() {
cin >> n;
jc[0] = 1;
for (int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mo;
njc[n] = ksm(jc[n], mo - 2);
for (int i = n - 1; ~i; i--) njc[i] = njc[i + 1] * (i + 1) % mo;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
key[a[i]] = 1;
}
getg();
f[2 * n + 1][0] = 1;
int cnt[2];
cnt[0] = cnt[1] = 0;
for (int i = 2 * n; i; i--) {
//不中的cnt[0]个点要放在1~j之间,因此j>=cnt[0]
for (int j = cnt[0]; j <= cnt[1]; j++)
if (f[i + 1][j]) {
if (!key[i]) {
f[i][j] = (f[i][j] + f[i + 1][j] * (2 * j - (j + cnt[0]))) % mo;
} else {
f[i][j] = (f[i][j] + f[i + 1][j]) % mo; //不放颜色的情况
for (int k = 1; j + k <= cnt[1] + key[i]; k++) {
f[i][j + k] = (f[i][j + k] + f[i + 1][j] * C(cnt[1] - j, k - 1) % mo *
g[k - 1][k - 1] % mo * (k + 1)) %
mo;
}
}
}
cnt[key[i]]++;
}
cout << f[1][n] * ksm((mo + 1) / 2, n) % mo << endl;