区间动态规划例题:合唱队形问题(最长递增递减子序列)
题目描述
有n位同学站成一排,音乐老师要请其中的(n-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,则他们的身高满足T1 < T2 < … < Ti > Ti+1 > … > TK (1 ≤ i ≤ K)。你的任务是,已知所有n位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
解题过程
-
问题分析
合唱队形的要求是:先严格递增,到达一个峰值后,再严格递减。这本质上是要找到一个“山顶”位置,使得在这个位置左边是最长递增子序列(LIS),右边是最长递减子序列(LDS)。注意,这个山顶位置的同学同时属于左右两个序列。我们的目标是最小化出列人数,也就是最大化能留在队形中的人数。能留在队形中的人数等于:对于每一个位置i,假设它作为山顶,那么以它结尾的LIS长度(
left[i])加上以它开头的LDS长度(right[i])再减去1(因为山顶同学被计算了两次)。然后我们遍历所有可能的i,找到这个和的最大值max_val。那么最少的出列人数就是n - max_val。 -
状态定义
这是一个经典的序列问题,虽然通常用线性DP解决,但其核心思想(从左到右和从右到左分别计算状态)与区间DP的“分割”思想相通。我们定义两个数组:left[i]:表示以第i个同学为结尾的最长严格递增子序列的长度。right[i]:表示以第i个同学为开头的最长严格递减子序列的长度。(也可以理解为,从右向左看,以第i个同学为结尾的最长严格递增子序列的长度)。
-
状态转移方程
-
计算
left数组(从左向右扫描):
对于每一个位置i(从1到n),初始化left[i] = 1(至少包含自己)。
遍历i之前的所有位置j(从1到i-1)。
如果height[j] < height[i](满足严格递增),那么就有状态转移:
left[i] = max(left[i], left[j] + 1)
这个方程的意思是:如果我在j处已经有一个递增序列,并且i比j高,那么我可以把i接在j所在的这个序列后面,形成一个更长的递增序列。 -
计算
right数组(从右向左扫描):
对于每一个位置i(从n到1),初始化right[i] = 1(至少包含自己)。
遍历i之后的所有位置j(从i+1到 n)。
如果height[j] < height[i](注意,因为是从右向左扫描,j在i的右边。要形成递减序列,右边的人应该比左边(山顶i)矮),那么就有状态转移:
right[i] = max(right[i], right[j] + 1)
这个方程的意思是:如果我在j处已经有一个(从右往左看的)递增序列(即正常的从左往右看的递减序列),并且i比j高,那么我可以把i接在j所在的这个序列前面,形成一个更长的递减序列。
-
-
求解最优解
在计算出left和right数组之后,我们遍历每一个位置i,计算如果以i为山顶,合唱队形最多能留下多少人:total_i = left[i] + right[i] - 1。
然后,我们找出所有total_i中的最大值max_total。
最终,需要出列的最少人数就是n - max_total。 -
举例说明
假设有8位同学,身高序列为:[186, 186, 150, 200, 160, 130, 197, 200]。-
计算
left数组(最长递增子序列):- i=1: 186 ->
left[1] = 1 - i=2: 186,前面186不满足
<->left[2] = 1 - i=3: 150,前面186,186都不满足
<->left[3] = 1 - i=4: 200,前面186,186,150都
<200,取最大left[j]是1,所以left[4] = 1+1=2 - i=5: 160,前面150
<160 ->left[5] = left[3]+1 = 2;186,186,200都不满足<160。 - i=6: 130,前面没有比130小的 ->
left[6] = 1 - i=7: 197,前面186,186,150,160,130都
<197,其中left最大的是left[4]=2,所以left[7] = 2+1=3 - i=8: 200,前面186,186,150,197都
<200,其中left最大的是left[7]=3,所以left[8] = 3+1=4
left = [1, 1, 1, 2, 2, 1, 3, 4]
- i=1: 186 ->
-
计算
right数组(从右向左计算最长递增子序列,即从左向右的最长递减子序列):- i=8: 200 ->
right[8] = 1 - i=7: 197,后面200不满足
<197 ->right[7] = 1 - i=6: 130,后面197,200都
<130? 不成立 ->right[6] = 1 - i=5: 160,后面130
<160 ->right[5] = right[6]+1=2;197,200都不满足<160。 - i=4: 200,后面160,130,197,200都不满足
<200 ->right[4] = 1 - i=3: 150,后面200,160,130,197,200都
<150? 不成立 ->right[3] = 1 - i=2: 186,后面150
<186 ->right[2] = right[3]+1=2;200,160,... 200不满足<186。 - i=1: 186,后面186,150,... 186不满足
<186,150<186 ->right[1] = right[3]+1=2;200不满足。
right = [2, 2, 1, 1, 2, 1, 1, 1](计算顺序是从右向左,但结果数组索引是从左向右)
- i=8: 200 ->
-
计算每个位置为山顶的
total:
total = left + right - 1
= [1+2-1, 1+2-1, 1+1-1, 2+1-1, 2+2-1, 1+1-1, 3+1-1, 4+1-1]
= [2, 2, 1, 2, 3, 1, 3, 4] -
找出最大值:
max_total = 4(在位置8) -
计算最少出列人数:
8 - 4 = 4。
因此,最少需要4位同学出列。一种可行的方案是出列身高为186, 150, 160, 130的同学,剩下
[186, 200, 197, 200](注意,这里山顶是最后一个200,其左边序列是[186, 200](递增),右边序列是[200](递减),但根据我们的计算,位置8的right[8]=1,所以它作为山顶时,右边的递减序列只有自己。更优的队形可能是[150, 200, 197, 200]?让我们检查一下位置4(200)作为山顶:left[4]=2(例如150,200),right[4]=1(只有自己),总数是2。位置7(197)作为山顶:left[7]=3(例如150,160,197 或者 150,200,197),right[7]=1,总数是3。所以最大的总数确实是位置8的4。队形可以是[150, 160, 197, 200]?但160>197不满足递减。实际上,[150, 200, 197, 200]是满足150<200>197<200,这不符合递减部分(197<200是递增了)。根据计算,正确的最大队形是4人,但具体是哪4人需要根据DP路径回溯,题目只要求出列人数。我们的计算结果是正确的。 -