UOJ#506. 【JOISC2020】遗迹 动态规划

题解

首先,我们尝试在给定高度排列的情况下,用一种简洁的方式求出最后还剩余的柱子的坐标是哪些。容易想到如下方式:

  • 设第 \(i(1\leq i \leq 2n)\) 个柱子的高度为 \(h_i\),然后设一个标记数组 \(f_{0..n}\),初始时 \(f\) 没有元素被标记。考虑按照柱子编号从大到小插入柱子。假设当前插入的柱子是 \(i\),则找到一个最大的 \(j\),使得 \(j\leq i\)\(f_j\) 没有被标记;如果 \(j=0\),那么该柱子最终没了,否则,该柱子是最后还剩余的柱子之一,并将 \(f_j\) 标记。

于是,我们发现只有当 \(f_1,f_2,\cdots,f_{h_i}\) 都被标记时,该柱子最终会消失。

接下来我们来考虑如何计数。

首先,同一个高度会出现两次,而且没有区别,比较棘手,我们可以将这两个值相同的高度看做不同的,并在最后给答案乘上 \(\frac{1}{2^n}\) 去重。

然后,我们设 \(F_i\) 表示使用 \(i+1\) 个柱子标记连续的长度为 \(i+1\) 的一段区间,并要求在使用前 \(i\) 个柱子时,该区间的第一个元素还没被标记。这可以通过简单的动态规划得到。后面的计算过程会使用 \(F_i\)

定义 \(g_{i,j}\) 表示当前加入了第 \(i,i+1,\cdots, 2n\) 个柱子,且 \(f_1,\cdots,f_j\) 被标记,且 \(f_{j+1}\) 没有被标记,且不考虑除了与标记这些位置相关的柱子以外的柱子,在这种情况下的方案数。接下来我们考虑 \(g_{i+1,j}\) 会对哪些值做贡献:

  • \(i+1\) 号到 \(2n\) 号柱子中有 \(cnt_0\) 个最终消失,有 \(cnt_1\) 个最终留下。
  • \(i\) 号柱子最终消失:则该柱子的高度必然小于等于 \(j\),在小于等于 \(j\) 的还没被使用的高度中任选一个,由于前 \(j\) 种高度总共有 \(2j\) 个选法,为了标记 \(1..j\),消耗了 \(j\) 个,为了使之前的 \(cnt_0\) 个柱子消失,消耗了 \(cnt_0\) 个,所以还剩 \(j-cnt_0\) 个选法,故 \(g_{i,j}+=(j-cnt_0)g_{i+1,j}\)
  • 否则:
    1. 如果当前柱子没有使 \(f_{j+1}\) 被标记,那么我们把给当前柱子取高度的问题放到之后解决,这里直接 \(g_{i,j}+=g_{i+1,j}\)
    2. 如果当前柱子使 \(f_{j+1..j+k+1}(0\leq k\leq cnt_1-j)\) 被标记(注意我们还有 \(cnt_1-j\) 个柱子还没有取高度),那么我们需要在给当前柱子取高度的同时从还没有取高度的 \(cnt_1-j\) 个柱子里选 \(k\) 个取高度,使得前面的条件满足,于是就有 \(g_{i,j+k+1}+=\binom{cnt_1-j}{k}F_kg_{i+1,j}\)

最后得到的 \(\frac{1}{2^n}g_{1,n}\) 就是答案了。

代码

#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
#define For(i,a,b) for (int i=(a);i<=(b);i++)
#define Fod(i,b,a) for (int i=(b);i>=(a);i--)
#define fi first
#define se second
#define kill _z_kill
#define pb(x) push_back(x)
#define mp(x,y) make_pair(x,y)
#define outval(x) cerr<<#x" = "<<x<<endl
#define outv(x) cerr<<#x" = "<<x<<"  "
#define outtag(x) cerr<<"--------------"#x"---------------"<<endl
#define outarr(a,L,R) cerr<<#a"["<<L<<".."<<R<<"] = ";\
	For(_x,L,R) cerr<<a[_x]<<" ";cerr<<endl;
#define User_Time ((double)clock()/CLOCKS_PER_SEC)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef unsigned uint;
typedef long double LD;
typedef vector <int> vi;
typedef pair <int,int> pii;
LL read(){
	LL x=0,f=0;
	char ch=getchar();
	while (!isdigit(ch))
		f=ch=='-',ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int mod=1e9+7;
int Pow(int x,int y){
	int ans=1;
	for (;y;y>>=1,x=(LL)x*x%mod)
		if (y&1)
			ans=(LL)ans*x%mod;
	return ans;
}
void Add(int &x,int y){
	if ((x+=y)>=mod)
		x-=mod;
}
void Del(int &x,int y){
	if ((x-=y)<0)
		x+=mod;
}
int Add(int x){
	return x>=mod?x-mod:x;
}
int Del(int x){
	return x<0?x+mod:x;
}
void ckmax(int &x,int y){
	if (x<y)
		x=y;
}
void ckmin(int &x,int y){
	if (x>y)
		x=y;
}
const int N=605;
int n;
int a[N*2];
int C[N][N],f[N][N],F[N],g[N*2+1][N];
int main(){
	n=read();
	For(i,1,n)
		a[read()]=1;
	For(i,0,n){
		C[i][0]=1;
		For(j,1,i)
			C[i][j]=Add(C[i-1][j-1]+C[i-1][j]);
	}
	f[0][0]=1;
	For(i,1,n)
		For(j,0,i-1){
			int v=f[i-1][j];
			if (!v)
				continue;
			Add(f[i][j],v);
			if (j+1<=i)
				Add(f[i][j+1],(LL)v*(j+1)*2%mod);
			if (j+2<=i)
				Add(f[i][j+2],(LL)v*(j+1)*(j+2)%mod);
		}
	For(i,0,n-1)
		F[i]=(LL)f[i][i]*(i+2)%mod;
	g[n*2+1][0]=1;
	int cnt0=0,cnt1=0;
	Fod(i,n*2,1){
		For(j,0,n){
			int v=g[i+1][j];
			if (!v)
				continue;
			int rp0=j-cnt0;
			int r1=cnt1-j;
			if (a[i]==0)
				g[i][j]=((LL)v*rp0+g[i][j])%mod;
			else {
				Add(g[i][j],v);
				For(k,0,r1)
					g[i][j+k+1]=((LL)v*F[k]%mod*C[r1][k]+g[i][j+k+1])%mod;
			}
		}
		if (a[i]==0)
			cnt0++;
		else
			cnt1++;
	}
	int ans=(LL)g[1][n]*Pow(2,mod-1-n)%mod;
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-06-13 14:09  -zhouzhendong-  阅读(94)  评论(2编辑  收藏