P3205 [HNOI2010]合唱队 (区间dp)

P3205 [HNOI2010]合唱队 (区间dp)_第1张图片

 

[HNOI2010]合唱队 - 洛谷

分析

根据题意我们发现,小a每次排队的时候,会出现两种情况

  • 当前排入的人比初始队列中前一个人矮,排到最左边
  • 当前排入的人比初始队列中前一个人高,排到最右边

现在给定我们一个理想队列,然后根据这个理想队列推理出有多少种初始队列

根据这个理想队列,其实我们可以发现,固定住一个点之后,初始队列在他后面的人,一定是不断加入到这个点左右两边的。所以固定住这个点之后,可以模拟不断向左向右加人,来获得理想队列。加人实际上就是一个区间左右端点的问题

于是就可以从理想队列出发,运用区间dp的思想,不断枚举元区间,从而获得最后的答案

问题:上一个人是谁?区间左右端点?

根据这个问题,需要记录的状态应该如下

状态:左右端点,上一次插入在哪

P3205 [HNOI2010]合唱队 (区间dp)_第2张图片

状态表示:所有区间[i,j]还原成初始队列,且最后一次插入在 左/右 的集合

初始化:全部初始化为0

边界:根据第一次插入的情况,dp[i][j][1]=0或者dp[i][j][0]=1  (选择一个即可)!!!!

因为dp[i][i][0/1]都定义的话会重复计算

子集划分/状态计算

根据最后一次插入的情况,可以划分为如下不重不漏的四个子集

当前插入在左

  • 上一次是在右     
  • 上一次是在左     

当前插入在右

  • 上一次是在右    >a[j-1]        dp[i][j][1]=max(dp[i][j-1][1])
  • 上一次是在左    >a[i]           dp[i][j][1]=max(dp[i][j-1][0])    
#include 
#include 
using namespace std;
const int N = 1005;
const int mod = 19650827;
typedef long long ll;
int a[N];
int b[N];
ll dp[N][N][2];
int main()
{
	int n;
	cin >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i], b[i] = a[i];
	for (int len = 1;len <= n;len++)
		for (int i = 1;i + len - 1 <= n;i++)
		{
			int j = i + len - 1;
			if (len == 1) dp[i][j][1]  = 1;
			//else if (len == 2) dp[i][j][0] = dp[i][j][1] = 1;
			else
			{
				ll& x = dp[i][j][0];
				ll& y = dp[i][j][1];
				if (a[i] < a[j])
					x = (x + dp[i + 1][j][1]) % mod;
				if (a[i] < a[i + 1])
					x = (x + dp[i + 1][j][0]) % mod;
				if (a[j] > a[i])
					y = (y + dp[i][j - 1][0]) % mod;
				if (a[j] > a[j - 1])
					y = (y + dp[i][j - 1][1]) % mod;
			}
		}
	ll res = (dp[1][n][0] + dp[1][n][1])%mod;
	cout << res;

}

你可能感兴趣的:(动态规划,思维,算法)