[JOISC 2020 Day2] 遗迹
一、题目
二、解法
首先理解删除的过程,可以按值域从高到低扫描。可以维护一个堆,每次向堆中加入两个初始值为 \(i\) 的位置,然后取出堆中最大的位置,钦定它为高度 \(i\) 并且可以保留到最后。
这样还不是特别好做,考虑切换限制主体得到更多性质。我们按位置从后到前扫描,维护一个还未出现过的集合 \(S\),假设现在遇到元素的高度是 \(h_i\),那么我们取出 \(S\) 中 \(\leq h_i\) 的最大值(若不存在视为 \(0\)),作为这个位置最终的值。
那么从后往前 \(dp\),考虑记录一段连续的被取出的前缀,可以用类似 MEX counting 的状态定义和转移方法。设 \(f[i][j]\) 表示考虑后 \(i\) 个位置,\(S\) 中被取出的最大前缀是 \(j\) 的方案数,设以前的位置有 \(c_0\) 个位置钦定消失,有 \(c_1\) 个位置钦定出现。
为了方便转移,把相同高度的元素染色(视为不同),最后再除以 \(2^n\) 就得到了答案。
若当前位置钦定消失,则值肯定不能超过 \(j\),以前钦定出现了用了 \(j\) 个,钦定消失了用了 \(c_0\) 个,可用的位置数量是 \(2j-j-c_0=j-c_0\),所以:\(f[i][j]\leftarrow f[i-1][j]\cdot (j-c_0)\)
若当前位置钦定出现,分两种情况讨论:
- 如果最后的值 \(>j+1\),那么延后考虑这个位置的取值:\(f[i][j]\leftarrow f[i-1][j]\)
- 如果最后的值 \(=j+1\),枚举前缀的变化量 \(k\),那么这个位置的取值有 \(2k-(k-1)=k+1\) 个。选择 \(k-1\) 个元素出现位置的方案数是 \({c_1-j\choose k-1}\),那么:\(f[i][j+k]\leftarrow f[i-1][j]\cdot G[k-1]\cdot (k+1)\cdot {c_1-j\choose k-1}\)
其中 \(G[i]\) 的含义为可用 \(i\) 种初始高度,要求生成最后 \(i\) 个高度连续元素的方案数。设 \(g[i][j]\) 表示用了 \(i\) 种初始高度,填满了 \(j\) 个位置,那么 \(G[i]=g[i][i]\),转移的限制是:
- 每种高度只能用最多两次。
- 要一直满足 \(i\geq j\),即填多了位置是不合法的。
那么可以写出转移 \(g[i][j]=g[i-1][j]+g[i-1][j-1]\cdot 2j+g[i-1][j-2]\cdot j(j-1)\)
时间复杂度 \(O(n^3)\)
#include <cstdio>
const int M = 1205;
const int MOD = 1e9+7;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[M],g[M][M],f[M][M],C[M][M];
void add(int &x,int y) {x=(x+y)%MOD;}
signed main()
{
n=read();m=n<<1;
for(int i=1;i<=n;i++) a[read()]=1;
for(int i=0;i<=m;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}
g[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
{
g[i][j]=g[i-1][j];
if(j>0) add(g[i][j],2*j*g[i-1][j-1]);
if(j>1) add(g[i][j],j*(j-1)*g[i-1][j-2]);
}
f[m+1][0]=1;
for(int i=m,c0=0,c1=0;i>=1;i--)
{
if(a[i])
{
c1++;
for(int j=c0;j<=c1-1;j++)
{
add(f[i][j],f[i+1][j]);
for(int k=1;k<=c1-j;k++)
add(f[i][j+k],f[i+1][j]*C[c1-j-1][k-1]
%MOD*g[k-1][k-1]%MOD*(k+1));
}
}
else
{
c0++;
for(int j=c0;j<=c1;j++)
add(f[i][j],f[i+1][j]*(j-c0+1));
}
}
int ans=f[1][n],inv2=(MOD+1)/2;
for(int i=1;i<=n;i++) ans=ans*inv2%MOD;
printf("%lld\n",ans);
}