洛谷 P3205 [HNOI2010] 合唱队 题解

题目链接

由于每个人可以从左边和右边两个方向进入,又因为求方案数,可以想到区间 dp。

新数从左进还是右进,很不好处理,怎么办呢?可以增添一维状态,把 \(l\)\(r\) 分别当作新加入的数进行分类讨论,可以很好地解决。

状态表示:

\(dp_{l,r,0}\) 表示区间 \([l,r]\),把 \(l\) 当作新加入的数的方案数。

\(dp_{l,r,1}\) 表示区间 \([l,r]\)\(r\) 当作新加入的数的方案数。

状态转移:

\(dp_{l,r,0}\) 为例,\(dp_{l,r,1}\) 同理。

\(l\) 从左边加入,那么加入前的区间为 \([l+1,r]\),前一个加入的数只能是 \(l+1\) 或者 \(r\)。我们可以分前一个加入的数这两种情况讨论,如果是 \(l+1\),那么需要满足 \(a_l<a_{l+1}\),如果是 \(r\) 那么需要满足 \(a_1<a_r\)

于是可以得出状态转移:

if(a[j+1]>a[j]) dp[j][r][0]=(dp[j][r][0]+dp[j+1][r][0])%MOD;
if(a[r]>a[j]) dp[j][r][0]=(dp[j][r][0]+dp[j+1][r][1])%MOD;
if(a[j]<a[r]) dp[j][r][1]=(dp[j][r][1]+dp[j][r-1][0])%MOD;
if(a[r-1]<a[r]) dp[j][r][1]=(dp[j][r][1]+dp[j][r-1][1])%MOD;

对于初始化,只有一个数的时候无论是从左进还是从右进,方案数都是 \(1\)。但是如果两种情况都初始化,后面转移时会导致把一个数的时候左进和右进两种情况都计入导致重复,所以只需要初始化左进。

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=1100;
const ll MOD=19650827;
int n,a[N],r;
ll dp[N][N][3],ans;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		dp[i][i][0]=1;
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j+i-1<=n;j++)
		{
			r=j+i-1;
			if(a[j+1]>a[j]) dp[j][r][0]=(dp[j][r][0]+dp[j+1][r][0])%MOD;
			if(a[r]>a[j]) dp[j][r][0]=(dp[j][r][0]+dp[j+1][r][1])%MOD;
			if(a[j]<a[r]) dp[j][r][1]=(dp[j][r][1]+dp[j][r-1][0])%MOD;
			if(a[r-1]<a[r]) dp[j][r][1]=(dp[j][r][1]+dp[j][r-1][1])%MOD;
		}
	}
	ans=(dp[1][n][0]+dp[1][n][1])%MOD;
	printf("%lld",ans);
	return 0;
}
posted @ 2025-06-14 14:31  MinimumSpanningTree  阅读(4)  评论(0)    收藏  举报