0%

动态规划--寻找最长递减子序列

昨天C++课上留了三道题,除了C语言本身外都涉及了一些算法。其中第二个问题是这样的:

拦截导弹

某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。

输入

第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25),

第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。

输出

输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。

样例输入

8

300 207 155 300 299 170 158 65

样例输出

6

课堂上没搞出来。老师说用到了动态规划。好吧,高中没学过信息学竞赛。不过没关系,回去翻了翻《算法导论》,基本算弄明白了。其实就是求一串数的最长递减子序列。

问题关键在于,可能的情况组合很多,不适合穷举。所以要利用动态规划(很适合这类问题)。动态规划把问题分成很多子问题,而子问题之间又有关系。子问题不是相互独立的,子问题可能有重叠,全局最优解由子问题的最优解构成(最优子结构性质)。这和分治法(典型的比如归并排序)不一样。

动态规划的基本想法在于:

  1. 描述最优解结构,就是说最优解是什么样子的,符合什么条件。
  2. 递归定义最优解的值。也就是写出状态方程。其中一般需要特别考虑初始条件。
  3. 自底向上计算最优解的值。
  4. 由计算的值构造最优解(在这题里没要求,到第三部求出序列长就行了)。

其中,问题设计时最关心的就是第二步。以下的步骤是必不可少的:

  • 阶段划分。
  • 确定状态变量。当前状态变量是由之前阶段的某个状态转移来的。当前状态是对之前所有状态的完整总结。当前这个状态之后的选择只受当前最优决策序列的影响(无后效性)。
  • 写状态转移方程。也就是确定如何从之前状态得到本阶段的状态。
  • 找边界条件(开始和终止的条件)。

比如说,这个问题中要拦第i个导弹。截止到第i个导弹的最长拦截序列肯定包含了前面i-1个导弹中最长的序列。如果不是这样,我们求出的截止到第i个导弹的拦截序列就不是最长的了。我们用数组D[i]把截止到第i个导弹的最长拦截序列长度存起来。最后计算完i=k(导弹总数)时,整个问题的答案就是数组D[i]的最大值。

如果用一个数组,把前一个最优解对应的导弹编号存下来,最后还可以把被击落的导弹高度按编号输出。

代码:

/*
    设数组A存放导弹高度,D[i]是高度为A[i]是的最优解,S用于记录最优解中导弹被击落的次序
    最优解结构:设在第k个数处的最优解为D(k)。设当前位于第i个数,则在第i个数处的
            最优解D(i)为:在i之前拦下导弹最多的方法的解D(j)加上1。(因为把第i
            个也打下来了,所以加上1)。当然还要保证A[i]<=A[j]
    递归解:D(i)=D(j)+1。j是数组A中位于A[i]左侧,且大于等于A[i]的所有数中D值最大的
            那个。如果A[i]左侧没有找到大于等于它的数,则D[i]=1。
    状态转移方程:D(i)=max{D(j)+1}  (0<=j<i<k)
                D(0)=1
    最终结果:找到数组D中最大的数,这个数就是结果。
*/
#include <cstdio>
int main(){
    int k;
    scanf("%d", &k);
    int A[k], D[k], S[k];

    for(int i=0; i<k; i++){
        scanf("%d", &A[i]);
        D[i]=0;
    }
    D[0]=1;

    for(int i=1; i<k; i++){
        bool found=false;   //是否找到i之前的最优解
        int last_good_solve=D[0];
        int good_position=0;//最优解的下标和值
        for(int j=0; j<i; j++)
            if(A[j]>=A[i] && D[j]>=D[good_position]){
                last_good_solve=D[j];
                good_position=j;
                found=true;
            }

        if(found){
            D[i]=1+D[good_position];
            S[i]=good_position;
        }
        else {
            D[i]=1;
            S[i]=good_position;
        }
    }

    int result=0;   int go_back;//用于向前找被击落的导弹
    for(int i=0; i<k; i++){
        if(D[i]>result){
            result=D[i];
            go_back=i;
        }
    }

    printf("%d\n", result);

    for(int i=0; i<result; i++){
        printf("%d  ", A[go_back]);
        go_back=S[go_back];
    }//逆序输出被击落导弹的高度
}
pic1