合唱队
问题描述:
计算最少出列多少位同学,使得剩下的同学排成合唱队形。
说明:
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位
同学排成合唱队形。
合唱队形是指这样的一种队形:
设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,
则他们的身高满足存在i(1<=i<=K)
使得T1<T2<......<Ti-1< Ti >Ti+1>......>TK。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入描述:
整数N
输出描述:
最少需要几位同学出列
示例1
输入
8
186 186 150 200 160 130 197 200
输出
4
问题解析
算法原理:动态规划
涉及概念:最长递增子序列
解题思路 :在N位同学中找出一个同学i,经过去除几位同学后,使得i为一个分割点,使得i之前的同学身高都小于i,i之后的同学身高都大于i。
若使去除的同学最少,则留下的同学应最大。
解题说明:
取示例 A = {186, 186, 150, 200, 160, 130, 197, 200}
(1)首先对A中所有的元素,计算每个元素在其正向最长递增子序列中的位置:
例如A[4] = 160,其所在最长子序列为{150,160,197,200},故所在位置为2。
最长子序列计算方法见上篇文章:超简单看明白如何求最长子序列-动态规划
计算结果如下:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
A[i] | 186 | 186 | 150 | 200 | 160 | 130 | 197 | 200 |
位置 | 1 | 1 | 1 | 2 | 2 | 1 | 3 | 4 |
(2)对A中的所有元素,计算每个数在反向递增子序列中的位置,首先对A进行逆序排列,再计算,
计算结果如下:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
A[i] | 186 | 186 | 150 | 200 | 160 | 130 | 197 | 200 |
位置 | 3 | 3 | 2 | 3 | 2 | 1 | 1 | 1 |
(3)对于A中每个元素i:
计算其位于正向递增子序列中的位置,则表示位于i前面,小于A[i]且保持递增的有多少个元素;
计算其位于反射递增子序列中的位置,则表示位于i后面,大于A[i]且保持递减的有多少个元素;
将每个元素所在正向递增子序列中的位置+所在反向递减子序列中的位置相加,取最大者对应的下标i,即为所求的分界点i。
计算结果如下:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
A[i] | 186 | 186 | 150 | 200 | 160 | 130 | 197 | 200 |
正向位置 | 1 | 1 | 1 | 2 | 2 | 1 | 3 | 4 |
反向位置 | 3 | 3 | 2 | 3 | 2 | 1 | 1 | 1 |
正向累加 | 4 | 4 | 3 | 5 | 4 | 2 | 4 | 5 |
取最大值i = 4,此时该元素所在队列的长度即为 5 - 1 = 4。
减1说明:
由于正反累加时,当前元素计算2次,例如当i = 4时,对于160这个数,
所在递增序列为{150,160,,,},位置为2
所在反向递增序列为{130,160,,,},位置为2
若取i = 4为分界点,则160所在的队列为{150,160,130},队列长度为 2 + 2 - 1 = 3。
(4)因此需要出队的人数 = 总人数 - 队列长度
针对本示例,应为 8 - (5 - 1) = 4。
代码如下:
int main()
{
int n = 0;
while(cin>>n)
{
int m = 0;
vector<int> dp1(n,1);//记录正向位置
vector<int> dp2(n,1);//记录反向位置
vector<int> vecints;
for(unsigned int i = 0; i < n; i++)
{
cin >> m;
vecints.push_back(m)
}
//计算正向位置
for(unsigned int i = 0; i < n; i++)
{
for(unsigned int j = 0; j < i; j++)
{
if(vecints[j] < vecints[i] && dp1[j] >= dp1[i])
dp1[i] = dp1[j] + 1;
}
}
//反转序列
reverse(vecints.begin(),vecints.end());
//计算反向位置
for(unsigned int i = 0; i < n; i++)
{
for(unsigned int j = 0; j < i; j++)
{
if(vecints[j] < vecints[i] && dp2[j] >= dp2[i])
dp2[i] = dp2[j] + 1;
}
}
//反转位置
reverse(dp2.begin(),dp2.end());
int Max = -1;//记录最大数列
for (unsigned int i = 0; i < n; i++)
{
Max = Max < dp1[i] + dp2[i] ? dp1[i] + dp2[i] : Max;
}
cout << n - Max + 1 << endl;
}
return 0;
}