P3205 【HNOI2010】合唱队

# $Description$

题面

\(n\)个同学按顺序排成一队,他们都有一个身高\(H_i\),这个顺序称为初始队形

现在按照以下法则重新站队:

对于第一个同学,直接站到新队伍中,队友后面每个同学,假如在初始队伍中他比前面的同学高则站到新队伍最右边,否则站到新队伍最左边(数据保证每个同学身高不同)。所有同学以这个方法站队,形成的新队列称为最终队列。

现在给你最终队列每个人的高度,问有多少种初始队列可以通过以上法则变成最终队列,答案对\(19650827\)取模。

\(Solution\)

考虑到站队的这个过程,每次让一个同学进入新队列只能到队伍的最左边或者队伍的最右边(假如初始队列为空,为避免重复考虑记为最左边),且区间长度\(+1\),整个过程是最终队列中的一个小区间向两边扩展。显然这道题可以使用区间\(DP\)解决,状态设计类似于“关路灯”这道题(仅仅是状态设计,转移过程没什么关系)。

我们设\(dp[i][j]\)表示初始队列从左往右站队已经完成了最终队列的\(i-j\)部分,发现这根本没法转移,再设计一维表示从左边插入还是从右边插入,才能利用题目条件中的法则进行转移。用\(dp[i][j][0]\)表示最终队列已经完成了\(i-j\)且最新一个人从左边插入,\(dp[i][j][1]\)表示最新一个人从右边插入。转移只有四种情况:

\(1.\)当前人从左边插入和上一个人从左边插入(要求\(H[i]<H[i+1]\),因为我们确定\(i+1\)是上个人的位置,\(H[i+1]\)就是上个人身高)

\(2.\)当前人在左边插入,上一个人从右边插入(要求\(H[i]<H[j]\)

\(3.\)当前人在右,上个人在左(要求\(H[j]>H[i]\))

\(4.\)当前人在右,上个人在右(要求\(H[j]>H[j-1]\)

初始化\(dp[i][i][0]=1,dp[i][i][1]=0\),默认每个人从左进队(反过来也行),否则答案会重复计算。

\(Code\)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define mod 19650827
#define re register
#define maxn 2010
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,a[maxn],dp[maxn][maxn][2];
int main()
{
	n=read();
	for(re int i=1;i<=n;++i) a[i]=read(),dp[i][i][0]=1;
	for(re int len=2;len<=n;++len)
		for(re int i=1;i<=n;++i)
		{
			int j=i+len-1;
			if(a[i]<a[i+1]) dp[i][j][0]+=dp[i+1][j][0];
			if(a[i]<a[j]) dp[i][j][0]+=dp[i+1][j][1];
			if(a[j]>a[j-1]) dp[i][j][1]+=dp[i][j-1][1];
			if(a[j]>a[i]) dp[i][j][1]+=dp[i][j-1][0];
			dp[i][j][1]%=mod;
			dp[i][j][0]%=mod;
		}
	printf("%d",(dp[1][n][1]+dp[1][n][0])%mod);
	return 0;
}
posted @ 2019-10-22 14:23  __Liuz  阅读(142)  评论(0编辑  收藏  举报