[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);
}
posted @ 2022-06-11 08:58  C202044zxy  阅读(329)  评论(0编辑  收藏  举报