合唱团(动态规划)

一、动态规划

1.动态规划的本质:是对问题状态的定义状态转移方程的定义。
1.1动态规划过程:每次决策依赖于当前状态。又随即引起状态的转移。
一个决策序列就是在变化的状态中产生出来的,所以,这样的多阶段最优化决策解决这个问题的过程就称为动态规划
2.动态规划是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推的(分治)的方式去解决。
如何拆分问题,是动态规划的核心。
2.1什么是状态的定义?

给定一个数列,长度为N,
求这个数列的最长上升(递增)子数列(LIS)的长度.
以
1 7 2 8 3 4
为例。
这个数列的最长递增子数列是 1 2 3 4,长度为4;
次长的长度为3, 包括 1 7 8; 1 2 3 等.

要解决这个问题,我们首先要定义这个问题和这个问题的子问题。
重新定义这个问题:

给定一个数列,长度为N,
设Fk:以数列中第k项结尾的最长递增子序列的长度。
求F1...FN中的最大值。

这个问题与原问题等价。
对于FK来说,F1,F2…都是它的子问题,因为以第k项结尾的最长递增子序列(下称LIS),包含着以第1..k-1中某项结尾的LIS。
上述的新问题Fk也可以叫做状态,定义中的“Fk为数列中第k项结尾的LIS的长度”,就叫做对状态的定义。
之所以把Fk做“状态”而不是“问题” ,一是因为避免跟原问题中“问题”混淆,二是因为这个新问题是数学化定义的。
2.2 什么是状态转移方程?
上述状态定义好之后,状态和状态之间的关系式,就叫做状态转移方程。比如,对于LIS问题,定义:
设FK为:以数列中第k项结尾的最长递增子序列的长度.
设A为题中数列,状态转移方程为:

F1=1(根据状态定义导出边界情况)
Fk=max(Fi+1|Ak>Ai)(k>1)

用文字解释一下是:以第k项结尾的LIS的长度是:保证第i项比第k项小的情况下,以第i项结尾的LIS长度加一的最大值,取遍i的所有值(i小于k)。
动态规划求解问题的一般步骤:

(1)描述最优解的结构

(2)递归定义最优解的值

(3)按自底向上的方式计算最优解的值

(4)由计算出的结果构造一个最优解

二、合唱团问题

问题描述:

有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述:

每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。

输出描述:

输出一行表示最大的乘积。

示例1
输入

3
7 4 7
2 50

输出

49

Java实现问题求解

package com.cn.test1;

import java.util.Scanner;

/**
 * 合唱团问题
 * 题目描述:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,
 * 要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
 * @author hackerlee
 * 1. 题目分析
 *题目要求n各学生中选择k个,使这k个学生的能力值乘积最大。这是一个最优化的问题。另外,在优化过程中,提出了相邻两个学生的位置编号差不超过d的约束。
 *
 *如果不用递归或者动态规划,问题很难入手,并且,限制条件d也需要对每一个进行约束,编程十分复杂所以,解决的方法是采用动态规划(理由:1.求解的是最优化问题;2.可以分解为最优子结构)
 *2. 问题分解
 *
 *1.对该问题的分解是关键。从n个学生中,选择k个,可以看成是:先从n个学生里选择最后1个,然后在剩下的里选择k-1个,并且让这1个和前k-1个满足约束条件
 *
 *2.数学描述
 *
 *   为了能够编程实现,需要归纳出其递推公式,而在写递推公式之前,首先又需要对其进行数学描述
 *
 *记第k个人的位置为one,则可以用f[one][k]表示从n个人中选择k个的方案。然后,它的子问题,需要从one前面的left个人里面,
 *选择k-1个,这里left表示k-1个人中最后一个(即第k-1个)人的位置,因此,子问题可以表示成f[left][k-1].
 *
 *    学生能力数组记为arr[n+1],第i个学生的能力值为arr[i]
 *    one表示最后一个人,其取值范围为[1,n];
 *    left表示第k-1个人所处的位置,需要和第k个人的位置差不超过d,因此
 *    max{k-1,one-d}<=left<=one-1
 *
 *在n和k定了之后,需要求解出n个学生选择k个能力值乘积的最大值。因为能力值有正有负,所以
 *
 *   当one对应的学生能力值为正时,
 *    f[one][k] = max{f[left][k-1]arr[i]}(min{k-1,one-d}<=left<=one-1);
 *    当one对应的学生能力值为负时
 *    f[one][k] = max{g[left][k-1]arr[i]}(min{k-1,one-d}<=left<=one-1);
 *    此处g[][]是存储n个选k个能力值乘积的最小值数组
 *
 */
public class Choir {

    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()) {
            //总人数
            int n = sc.nextInt();
            //学生能力值数组,第i个人直接对应arr[i]
            int[] arr = new int[n + 1];
            //初始化
            for (int i = 1; i <= n; i++) {//人直接对应坐标
                arr[i] = sc.nextInt();
            }
            //选择的学生数
            int kk = sc.nextInt();
            //间距
            int dd = sc.nextInt();

            /**
             * 递推的时候,以f[one][k]的形式表示
             * 其中:one表示最后一个人的位置,k为包括这个人,一共有k个人
             * 原问题和子问题的关系:f[one][k]=max{f[left][k-1]*arr[one],g[left][k-1]*arr[one]}
             */
            //规划数组
            long[][] f = new long[n + 1][kk + 1];//人直接对应坐标,n和kk都要+1
            long[][] g = new long[n + 1][kk + 1];
            //初始化k=1的情况
            for(int one = 1;one<=n;one++){
                f[one][1] = arr[one];
                g[one][1] = arr[one];
            }
            //自底向上递推
            for(int k=2;k<=kk;k++){
                for(int one = k;one<=n;one++){
                    //求解当one和k定的时候,最大的分割点
                    long tempmax = Long.MIN_VALUE;
                    long tempmin = Long.MAX_VALUE;
                    for(int left = Math.max(k-1,one-dd);left<=one-1;left++){
                        if(tempmax<Math.max(f[left][k-1]*arr[one],g[left][k-1]*arr[one])){
                            tempmax=Math.max(f[left][k-1]*arr[one],g[left][k-1]*arr[one]);
                        }
                        if(tempmin>Math.min(f[left][k-1]*arr[one],g[left][k-1]*arr[one])){
                            tempmin=Math.min(f[left][k-1]*arr[one],g[left][k-1]*arr[one]);
                        }
                    }
                    f[one][k] = tempmax;
                    g[one][k] = tempmin;
                }
            }
            //n选k最大的需要从最后一个最大的位置选
            long result = Long.MIN_VALUE;
            for(int one = kk;one<=n;one++){
                if(result<f[one][kk]){
                    result = f[one][kk];
                }
            }
            System.out.println(result);
        }
    }


}

猜你喜欢

转载自blog.csdn.net/ooo123lll/article/details/80088815
今日推荐