【JOISC 2020】【LOJ3276】【UOJ506】遗迹(组合数学DP)

题解:

如果知道原序列,我们可以倒序直接求出每个柱子的最终高度。

若一个柱子最终高度为 x x x ,则我们称 x x x 被占据了。

具体做法就是从 2 n 2n 2n 枚举到 1 1 1,如果之前处理后还有大于 1 1 1 小于等于 a i a_i ai 的位置没有被占据,显然该柱子会停留在最大的位置并占据它,否则最终会变为 1 1 1

这样我们可以横向顺次考虑这个问题,而不是每次都考虑整体,这也是我们 DP 的基础。

考虑每个消亡的柱子,它能够选择的高度显然取决于它右边留下的柱子所占据的位置从 1 1 1 开始的连续段长度。

那么我们要记录的信息就很明显了,设 f [ i ] [ j ] f[i][j] f[i][j] 表示考虑 [ i , 2 n ] [i,2n] [i,2n] 的柱子,从 1 1 1 开始占据的连续段长度为 n n n 的时候的方案数。

为了方便,我们假设每个长度都有两个本质不同的标记,比如两个长度为 1 1 1 的柱子,我们将它的长度视作 1 1 , 1 2 1_1,1_2 11,12,最终出来的结果相当于乘了 2 n 2^n 2n ,除掉即可。

如果某个位置为空,显然这个位置的初始长度有 j − c n t [ 0 ] j-cnt[0] jcnt[0] 种可能, c n t [ 0 ] cnt[0] cnt[0] 是这个位置之前消亡的位置数。

否则,我们需要考虑的情况也很明显,就是加上这个位置之后形成的新的连续段,如果没有形成新的连续段,我们将这个位置设为待定。考虑形成新的连续段长度为 j + k j+k j+k,那么这个柱子的最终高度为 j + 1 j+1 j+1,可选的高度是 ( j + 1 ) 1 , ( j + 1 ) 2 (j+1)_1,(j+1)_2 (j+1)1,(j+1)2,以及左边已经形成长度为 k − 1 k-1 k1 的连续段后剩下的 k − 1 k-1 k1 个长度,总共 k + 1 k+1 k+1 种长度。

然后需要考虑的就是 k k k 个柱子,全部落下来占据了 1 − k 1-k 1k 的长度且没有消亡的方案数。
这个直接考虑带标号拼接即可,除了连接处必须是当前新加的位置,其他都是可以选择的。

具体的DP实现可以看代码。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int mod=1e9+7,i2=mod/2+1;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int po(int a,int b){int r=1;for(;b;b>>=1,Mul(a,a))if(b&1)Mul(r,a);return r;}

cs int N=6e2+7;

int n;
int f[N],g[N],vs[N+N],C[N][N];

void Main(){
	scanf("%d",&n);
	for(int re i=0;i<n;++i){
		int x;scanf("%d",&x);vs[x]=true;
	}
	for(int re i=0;i<=n;++i){
		C[i][0]=1;
		for(int re j=1;j<=i;++j)
			C[i][j]=add(C[i-1][j],C[i-1][j-1]);
	}g[0]=1;
	for(int re i=1;i<=n;++i){
		for(int re j=0;j<i;++j)
			Inc(g[i],mul(mul(g[j],g[i-j-1]),mul(C[i-1][j],j+2)));
	}
	f[0]=1;int nw=0,nn=0;
	for(int re i=n+n;i;--i)
		if(vs[i]){
			for(int re j=nn+1;j>=0;--j)
				for(int re k=1;k<=j;++k)
					Inc(f[j],mul(mul(f[j-k],g[k-1]),mul(C[nn-(j-k)][k-1],k+1)));
			++nn;
		}else {
			for(int re j=0;j<=nn;++j)
				Mul(f[j],j-nw);
			++nw;
		}
	cout<<mul(f[n],po(i2,n))<<"\n";
}

inline void file(){
#ifdef zxyoi
	freopen("ruins.in","r",stdin);
#endif
}signed main(){file();Main();return 0;}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值