遗迹[JOISC 2020 Day2]

题目链接

链接

题解

先来看看给一个 $a_i$,最后柱子的高度 $b_i$ 可以怎么算。

显然可以从后面倒着枚举 $i$,然后从 $a_i$ 倒着枚举到 $0$ 找到一个 $j$ ,使得 $[1,j]$ 已经全部在 $i$ 后面使用过了。那么这个$b_i=j$。

这样就可以进行DP了,显然是倒着来的。

为了方便,我们设两个 $i$ 是不相同的,这样做就不需要处理是否出现过 $i$,那么只要最后答案除以 $2^n$ 就可以了。

设 $f _ {i,j} $ 表示从后往前考虑到第 $i$ 位时,$[1,j]$ 已经全部使用过,$j+1$ 没有使用的方案数。

  • 若这一位的 $b_i$ 为 $0$:

那么这一位的 $a_i$ 则必须选在 $[1,j]$ 中的数,假设后面有 $m$ 个位置答案为 $0$,也就是说,已经有 $m$ 个位置选了 $[1,j]$ 中的数,且 $[1,j]$ 中所有数至少过一次(不然不可能 $[1,j]$ 全部数都使用过),也就是还有 $2j-(m+j)=j-m$ 个数可以选。

因此有:$f_{i,j}=f_{i+1,j}\times (j-m)$。

  • 若这一位最后不为 $0$:

如果这一位最终不为 $j+1$,则只能以后再考虑。

如果这一位最终的 $b_i$ 为 $j+1$,那么再枚举这个 $j$ 转移到的 $j+k$,也就是当 $j+1$ 已经使用过后,$[1,j+k]$ 都将被使用过。下面考虑这个系数是什么。

设 $m$ 为 $[i+1,2n]$ 中,$b$ 不为 $0$ 的数的个数。

此时需要把 “如果这一位最终不为 $j+1$,则只能以后再考虑。”​的情况考虑进去。也就是需要搞出后面的数是如何让 $[j+1,j+k]$ 都使用过的方案数。那么 $[i+1,2n]$ 最终的 $b_i$ 中,$[j+2,j+k]$ 的数都只能且必须出现 $1$ 次,也就是 $\binom{m-j}{k-1}$。

然后考虑 $a_i$ 能选的方案数,显然是 $2+((j+k)-(j+2)+1)=k+1$ 中情况。

然后还剩下一个 $s_{k-1}$,其中 $s_{m}$ 表示在 $[1,m]$ ,每个数能选两次,一共选 $m$ 个,最终的 $b_i$ 在 $[1,m]$ 均出现的方案数。

这个 $s_{m}$ 显然也可以DP解决。设 $g_{i,j}$ 表示考虑到第 $i$ 个数,最终的 $b_i$ 在 $[1,j]$ 均出现的方案数。

那么考虑第 $i$ 个数选了多少个,有: $g_{i,j}=g_{i-1,j}+g_{i-1,j-1}\times 2j+g_{i-1,j-2}\times j(j-1)$。

则有:$s_m=g_{m,m}$。

时间复杂度 $O(n^3)$。

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <bits/stdc++.h>
using namespace std;
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define fo(i,j,k) for(int i=(j),end_i=(k);i<=end_i;i++)
#define fd(i,j,k) for(int i=(j),end_i=(k);i>=end_i;i--)
#define DEBUG(x) cout<<#x<<"="<<x<<endl
#define all(x) (x).begin(),(x).end()
#define cle(x) memset(x,0,sizeof(x))
#define lowbit(x) ((x)&-(x))
#define ll long long
#define ull unsigned ll
#define db double
#define lb long db
#define pb push_back
#define mp make_pair
#define fi first
#define se second
inline int read()
{
int x=0; char ch=getchar(); bool f=0;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return f?-x:x;
}
#define CASET fo(___,1,read())
const ll mod=1e9+7;
inline ll Add(ll x,ll y){x+=y; return (x<mod)?x:x-mod;}
inline ll Dec(ll x,ll y){x-=y; return (x<0)?x+mod:x;}
inline ll Mul(ll x,ll y){return x*y%mod;}
inline ll Pow(ll x,ll y){y%=(mod-1);ll ans=1;for(;y;y>>=1,x=x*x%mod)if(y&1) ans=ans*x%mod;return ans;}
const int N=1205;
int n,p[N],cnt[N];
ll fac[N],inv[N],g[N][N],f[N][N];
inline void init(int n)
{
fac[0]=1; fo(i,1,n) fac[i]=fac[i-1]*i%mod;
inv[n]=Pow(fac[n],mod-2); fd(i,n,1) inv[i-1]=inv[i]*i%mod;
}
inline ll C(int n,int m)
{
if(n<0||m<0||n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
inline void calc_g(int n)
{
g[0][0]=1;
fo(i,1,n) fo(j,0,i)
{
g[i][j]=g[i-1][j];
if(j>0) g[i][j]=Add(g[i][j],Mul(g[i-1][j-1],2*j));
if(j>1) g[i][j]=Add(g[i][j],Mul(g[i-1][j-2],j*(j-1)));
}
}
inline void calc_f(int n)
{
f[2*n+1][0]=1;
int m;
fd(i,2*n,1)
if(!p[i])
{
m=2*n-i-cnt[i+1];
fo(j,m,n) f[i][j]=Mul(f[i+1][j],j-m);
}
else
{
fo(j,0,n) f[i][j]=f[i+1][j];
m=cnt[i+1];
fo(j,0,m)
if(f[i+1][j])
fo(k,1,n-j)
f[i][j+k]=Add(f[i][j+k],Mul(Mul(f[i+1][j],C(m-j,k-1)),Mul(k+1,g[k-1][k-1])));
}
}

int main()
{
n=read();
fo(i,1,n) p[read()]=1;
fd(i,2*n,0) cnt[i]=cnt[i+1]+p[i];
init(2*n); calc_g(n); calc_f(n);
printf("%lld",Mul(f[1][n],Pow((mod+1)/2,n)));
return 0;
}