合唱队问题的动态规划解法

来源:互联网 发布:龚德柏 知乎 编辑:程序博客网 时间:2024/05/19 14:39
#include <stdlib.h>#include "oj.h"#include <iostream>using namespace std;int lASS(int *a,int n,bool(*pfun)(int,int),int* f){  f[1]=1; //数组f用来记录从元素1开始截止到下表i的最长递增子序列的长度 //这里f[1]必须初始化为 for(int i=2;i<=n;i++)//自底向上增加问题的规模,则f[n]就存储了从1到n的最长递增子序列的长度 {  f[i]=1;// 先初始化一个比较小的值  //for(int j=i-1;j>0;j--)//遍历第i个元素前面的所有元素  for(int j=1;j<i;j++)//遍历第i个元素前面的所有元素  {               if(f[j]+1>f[i])//如果到下标j的最长递增子序列长度+1比当前f[i]大      {      if(pfun(a[j],a[i])) //如果a[j]<a[i]           {           f[i]=f[j]+1;//则截止到i的最长递增子序列可增一                      }                    }               }//for  cout<<"f["<<i<<"]="<<f[i]<<endl;   } int max=1; for(int i=1;i<=n;i++)  if(max<f[i]) max=f[i]; return max;}//function  int lDSS(int *a,int n,bool(*pfun)(int,int),int* f){  f[n]=1; //数组f用来记录从元素1开始截止到下表i的最长递增子序列的长度 //这里f[1]必须初始化为 for(int i=n-1;i>0;i--)//自底向上增加问题的规模,则f[n]就存储了从1到n的最长递增子序列的长度 {  f[i]=1;// 先初始化一个比较小的值  //for(int j=i-1;j>0;j--)//遍历第i个元素前面的所有元素  for(int j=n;j>i;j--)//遍历第i个元素前面的所有元素  {            if(f[j]+1>f[i])//如果到下标j的最长递增子序列长度+1比当前f[i]大      {      if(pfun(a[j],a[i])) //如果a[j]<a[i]           {           f[i]=f[j]+1;//则截止到i的最长递增子序列可增一              }                 }               }//for  cout<<"f["<<i<<"]="<<f[i]<<endl; } int max=1; for(int i=1;i<=n;i++)  if(max<f[i]) max=f[i]; return max;}//function//#include "lASS.h"/*功能:计算最少出列多少位同学,使得剩下的同学排成合唱队形    输入参数:  int N : 最初队列的N位同学 (2<=N<=50)  char* statureArray:N位同学的身高字符串(保证只含数字和空格)          身高Ti要求(130<=Ti<=230)输出参数:   int* rst: 最少出列多少位同学,使得剩下的同学排成合唱队形返回值:  0: 成功; -1:异常 */bool parseArray(char* Sa,int* a,int n){  char* pstr=Sa;  cout<<endl;  for(int i=1;i<=n;i++)  {     a[i]=atoi(pstr);  pstr+=4;     cout<<"a["<<i<<"]"<<"is "<<a[i]<<endl;  }  return true;  }int chorus(int N, char* StatureArray, int* Rst){ int* a=new int[N+1];;    parseArray(StatureArray,a,N); int* f1=new int[N+1]; int* f2=new int[N+1];        cout<<"the longest ascend sub sequence"<<lASS(a,N,st,f1)<<endl;  cout<<"the  longest descend sub sequence"<<lDSS(a,N,st,f2)<<endl; if(f1[N]==N||f1[N]==N) return false;// 单调序列无法组成合唱队  int max=0;  for(int i=1;i<=N;i++)  if(max<f1[i]+f2[i]-1) max=f1[i]+f2[i]-1; *Rst=N-max; cout<<endl<<*Rst<<endl;   return 0;}  


问题描述:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,
则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。


功能描述:计算最少出列多少位同学,使得剩下的同学排成合唱队形
    
输入参数:
     int N : 最初队列的N位同学 (2<=N<=50)
     char* statureArray:N位同学的身高字符串(保证只含数字和空格)
             身高Ti要求(130<=Ti<=230)
输出参数:
     int* rst: 最少出列多少位同学,使得剩下的同学排成合唱队形
返回值:
        0: 成功; -1:异常

分析:问题的关键在于同学的排列顺序不能改变,这就降低了问题的复杂度,可以用动态规划来求解。


这样分析就是典型的最长下降子序列问题。只要枚举每一个人站中间时可以的到的最优解。显然它就等于,包括他在内向左求最长上升子序列,向右求最长下降子序列。

其实最长子序列只要一次就可以了。因为最长下降(上升)子序列不受中间人的影响。

只要用OPT1求一次最长上升子序列,OPT2求一次最长下降子序列。这样答案就是N-max<i>(opt1[i]+opt2[i]-1).

 

 

 

...........

思路:该题的关键是不能置换队员的顺序,这个要求降低了问题的难度,如果可以排列的话就难了,建一个堆总会出现重复的元素,如何调整还没想出来怎么做。

考虑不能排列的情况,先对整个队列求一个最长递增子序列再求一个最长递降子序列。则剩下的人数就好算了。因为最长递增子序列是一个典型的动态规划问题,设子问题的解是

FAscend[i]FDescend[i]则合唱队问题的最优解是N-max[i]{FAscend[i]+ FDescend[i]-1}

关键:最优子结构的性质,递归定义子问题的解,自底向上求解

一,     最长递增子序列问题的描述设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<kmaK1<ak2<…<akm。求最大的m值。

二,     最长递增子序列问题的最优子结构性质:

设f(i)表示序列中以ai为末尾元素的最长递增子序列的长度。在求以ai为末尾的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<I,且aj<ai。如果这样的元素存在,那么对于所有的aj,都有一个以aj为末尾元素的最长递增子序列的长度,把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)+1

F(i)=max[j]{f(j)+1},即以ai为末尾元素的最长递增子序列,等于以使f(j)最大的哪个aj为末尾元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末尾元素的递增子序列。

三, 实现:用f【】来保存最优值,外层循环扩大问题规模,即数组f的下表递增,内层循环遍历从i到1的子问题的最优质。

 

 

 implementation:

 

 

 


原创粉丝点击