本文主要是介绍动态规划__合唱队形问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
问题描述
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。 你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式 Input Format 输入的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。
输出格式 Output Format 输出包括得到的最优队列的同学个数 以及最终同学的身高排列
思路
这个问题可以分解为 对于0<=i<=n-1(n为同学数) 我们要求得满足子序列[0, i)的升序排列最优个数p加上子序列[i, n)满足降序最优个数q即p+q最大的i值 对于升序和降序的最优解 可以用动态规划来构造
数组a[i]是第i个人的身高,b[i]是从左边第一个到a[i]的最长上升子序列,c[i]是从右边第一个到a[i]的最长上升子序列。这题的关键在于理解如何获得b[i]和c[i]的值。我们可以知道,不管a[i]为什么值时,b[i]>=1;(因为它本身就是一个上升序列元素。)假设 i=1 ,此时b[1]=1;因为a[1]的前面没有元素。那么如果a[2]>a[1],显然可以得到b[2]=b[1]+1; 因为a[1],a[2]是一个上升子序列。如果a[3]<=a[2]且a[3]>=a[1],那么b[2]为2;因为它可以和a[1]组成上升子序列。同理,若a[3]>a[1]..a[3-1],则b[3]=max{b[1]...b[3-1]}。由此我们可以得到递推关系式: b[i]=max{b[j](1<=j<i)|a[i]> a[j]}+1 (学过集合的应该看得懂。)同理可以求出c的值。 然后,可以得到符合合唱队的队列是b[i]+c[i]-1(a[i]被重复计算了一次),而题目要求的合唱队列是:max{b[i]+c[i]-1} 1<=i<=总人数,那么要挑出去多少人也就明白了,即 总人数 - max{b[i]+c[i]-1
- //DJ.W 2012.11.9
- //合唱队形问题:
- // 问题描述: 给定一串整数 从中剔除一些数 使得其按从从小到大 再从大到小的顺序排列 如: 1 2 3 6 5 4 要求留下的数字尽可能多
- // 输入: 数组大小 数组数据
- // 输出: 能得到的最优队列元素个数 并输出元素队列
- #include <iostream>
- using namespace std;
- //用于保存子问题最优解的备忘录
- typedef struct
- {
- int maxlen; //当前子问题最优解
- int prev; //构造该子问题所用到的下一级子问题序号(用于跟踪输出最优队列)
- }Memo;
- //用于递归输出Memo B中的解
- void Display(int* A, Memo* M, int i)
- {
- if (M[i].prev == -1)
- {
- cout<<A[i]<<" ";
- return;
- }
- Display(A, M, M[i].prev);
- cout<<A[i]<<" ";
- }
- //算法主要部分
- void GetBestQuence(int* A, int n)
- {
- //定义备忘录 并作必要的初始化
- Memo *B = new Memo[n]; //B[i]代表从A[0]到A[i]满足升序剔除部分元素后能得到的最多元素个数
- Memo *C = new Memo[n]; //C[i]代表从A[i]到A[n-1]满足降序剔除部分元素后能得到的最多元素个数
- B[0].maxlen = 1; //由于B[i]由前向后构造 初始化最前面的子问题 (元素本身就是一个满足升序降序的序列)
- C[n-1].maxlen = 1; //同样C[i]由后向前构造
- for (int i=0; i<n; i++) //为前一个最优子问题序号给定一个特殊标识-1
- //用于在跟踪路径时终止递归或迭代(因为我们并不知道最终队列从哪里开始)
- {
- B[i].prev = -1;
- C[i].prev = -1;
- }
- for (i=1; i<n; i++) //构造B[n]
- {
- int max=1;
- for (int j=i-1; j>=0; j--) //查看前面的子问题 找出满足条件的最优解 并且记录
- {
- if (A[j]<A[i] && B[j].maxlen+1>max)
- {
- max = B[j].maxlen+1; //跟踪当前最优解
- B[i].prev = j; //跟踪构造路径
- }
- }
- B[i].maxlen = max; //构造最优解
- }
- for (i=n-1; i>0; i--)
- {
- int max=1;
- for (int j=i; j<n; j++) //从后往前构造 这是为了后面在统筹最终解时可以直接用B[i]+C[i]-1
- //否则我们得到的最优解始终为B[n-1]+C[n-1]
- {
- if (A[j]<A[i] && C[j].maxlen+1>max) //比当前长度更长 记录并构造
- {
- max = C[j].maxlen+1;
- C[i].prev = j;
- }
- }
- C[i].maxlen = max;
- }
- //遍历i 得到最大的B[i]+C[i]-1(-1是因为我们在B[i]和C[i]中 均加上了A[i]这个数 因此需要减去重复的)
- int maxQuence = 0; //记录当前最优解
- int MostTall; //记录i 用于跟踪构造路径
- for (i=0; i<n; i++)
- {
- if (B[i].maxlen+C[i].maxlen-1 > maxQuence)
- {
- maxQuence = B[i].maxlen+C[i].maxlen-1;
- MostTall = i;
- }
- }
- cout<<"最大合唱队形长度: "<<maxQuence<<endl;
- //B由前向后构造 因此prev指向前面的元素 需要递归输出
- Display( A, B, MostTall);
- //C的prev指向后面元素 直接迭代输出
- while (C[MostTall].prev != -1)
- {
- MostTall = C[MostTall].prev;
- cout<<A[MostTall]<<" ";
- }
- cout<<endl;
- delete []B;
- delete []C;
- }
- int main()
- {
- //测试
- int *A;
- int n;
- cout<<"请输入合唱队员个数: "<<endl;
- cin>>n;
- A = new int[n];
- cout<<"输入队员身高 :"<<endl;
- for (int i=0; i<n; i++)
- {
- cin>>A[i];
- }
- GetBestQuence(A, n);
- delete []A;
- return 0;
- }
//DJ.W 2012.11.9
//合唱队形问题:
// 问题描述: 给定一串整数 从中剔除一些数 使得其按从从小到大 再从大到小的顺序排列 如: 1 2 3 6 5 4 要求留下的数字尽可能多
// 输入: 数组大小 数组数据
// 输出: 能得到的最优队列元素个数 并输出元素队列
#include <iostream>
using namespace std;//用于保存子问题最优解的备忘录
typedef struct
{int maxlen; //当前子问题最优解int prev; //构造该子问题所用到的下一级子问题序号(用于跟踪输出最优队列)
}Memo;//用于递归输出Memo B中的解
void Display(int* A, Memo* M, int i)
{if (M[i].prev == -1){cout<<A[i]<<" ";return;}Display(A, M, M[i].prev);cout<<A[i]<<" ";
}//算法主要部分
void GetBestQuence(int* A, int n)
{//定义备忘录 并作必要的初始化Memo *B = new Memo[n]; //B[i]代表从A[0]到A[i]满足升序剔除部分元素后能得到的最多元素个数Memo *C = new Memo[n]; //C[i]代表从A[i]到A[n-1]满足降序剔除部分元素后能得到的最多元素个数B[0].maxlen = 1; //由于B[i]由前向后构造 初始化最前面的子问题 (元素本身就是一个满足升序降序的序列)C[n-1].maxlen = 1; //同样C[i]由后向前构造for (int i=0; i<n; i++) //为前一个最优子问题序号给定一个特殊标识-1 //用于在跟踪路径时终止递归或迭代(因为我们并不知道最终队列从哪里开始){B[i].prev = -1;C[i].prev = -1;}for (i=1; i<n; i++) //构造B[n]{int max=1;for (int j=i-1; j>=0; j--) //查看前面的子问题 找出满足条件的最优解 并且记录{if (A[j]<A[i] && B[j].maxlen+1>max){max = B[j].maxlen+1; //跟踪当前最优解B[i].prev = j; //跟踪构造路径}}B[i].maxlen = max; //构造最优解}for (i=n-1; i>0; i--){int max=1;for (int j=i; j<n; j++) //从后往前构造 这是为了后面在统筹最终解时可以直接用B[i]+C[i]-1//否则我们得到的最优解始终为B[n-1]+C[n-1]{if (A[j]<A[i] && C[j].maxlen+1>max) //比当前长度更长 记录并构造{max = C[j].maxlen+1;C[i].prev = j;}}C[i].maxlen = max;}//遍历i 得到最大的B[i]+C[i]-1(-1是因为我们在B[i]和C[i]中 均加上了A[i]这个数 因此需要减去重复的)int maxQuence = 0; //记录当前最优解int MostTall; //记录i 用于跟踪构造路径 for (i=0; i<n; i++){if (B[i].maxlen+C[i].maxlen-1 > maxQuence){maxQuence = B[i].maxlen+C[i].maxlen-1;MostTall = i;}}cout<<"最大合唱队形长度: "<<maxQuence<<endl;//B由前向后构造 因此prev指向前面的元素 需要递归输出Display( A, B, MostTall);//C的prev指向后面元素 直接迭代输出while (C[MostTall].prev != -1){MostTall = C[MostTall].prev;cout<<A[MostTall]<<" ";}cout<<endl;delete []B;delete []C;
}
int main()
{//测试int *A;int n;cout<<"请输入合唱队员个数: "<<endl;cin>>n;A = new int[n];cout<<"输入队员身高 :"<<endl;for (int i=0; i<n; i++){cin>>A[i];}GetBestQuence(A, n);delete []A;return 0;
}
这篇关于动态规划__合唱队形问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!