区间动态规划问题(游艇租用问题)

问题描述

长江游艇俱乐部在长江上设置了n个游艇出租站1,2,…,n。游客可在这些游艇出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站i到游艇出租站j之间的租金为r(i,j),1<=i<j<=n。试设计一个算法,计算出从游艇出租站1到游艇出租站n所需的最少租金。

编程任务

对于给定的游艇出租站i到游艇出租站j之间的租金为r(i,j),1£i<j£n,编程计算从游艇出租站1到游艇出租站n所需的最少租金。

数据输入

由文件input.txt提供输入数据。文件的第1行中有1个正整数n(n<=200),表示有n个游艇出租站。接下来的n-1行是r(i,j),1<=i<j<=n。

(例如:
3

5 15

7

表示一共有3个出租站点,其中

第1个站点到第2个的租金为5

第1个站点到第3个的租金为15

第2个站点到第3个的租金为7

)

模板

这是一个区间动态规划问题,状态的转移发生在一个个区间上。

针对该类题目,可从以下模板中找寻思路:

1. 阶段(区间长度)

设区间长度为d,

  • 当区间是1 =》 2的时候,此时区间长度为2;

  • 当区间是1 =》 3的时候,此时区间长度为3。

很显然,如果区间长度小于3,那就可以直接取值,不需要状态转移

所以我们只需要考虑区间长度 > 3 的情况

即 d = 3,4,…n (当 d = 2 的时候,也就是1-2,2-3,不需要求解,直接读取数组的值即可)

2. 状态

举例:当 区间长度d = 3 ,站点数n = 6 的时候

起点i的取值:1-3,2-4,3-5,4-6,不能再取5了,因为最大值n = 6,而5-6不能取到d=3,因
此i的取值为 1 ~ n - d + 1。

终点j的取值:i ~ n - i + 1

而中间可能停靠的站点k 就在i和j之间取值

3. 决策

状态转移方程:

用dp [ i ] [ j ]表示从i到j的最少费用

dp [ i ] [ j ] = min{dp [ i ] [ j ] , dp [ i ] [ k ] + dp [ k ] [ i ]}


扩展

如果需要记录最短费用的路径上,是停靠了哪些站点呢?

那就再开一个二维数组 s[ i ] [ j ],当发生状态转移的时候,就记录下当前的k值。

然后通过递归,依次找到其他不为0的k值。

public static void minPath(int i , int j , int [][]path, int []pathPoint,int pathIndex) {
        if(path[i][j] == 0){} // 什么都不做
        else {
            pathPoint[pathIndex++] = path[i][j]; // 加入到数组
            minPath(i,path[i][j],path,pathPoint,pathIndex); // 左边区间
            minPath(path[i][j],j,path,pathPoint,pathIndex); // 右边区间
        }
    }

完整代码:

package algorithm;

import java.io.*;
import java.util.Arrays;
import java.util.Scanner;

public class experiment_03 {


    public static void main(String[] args) {

        String srcPath = "Input.txt";
        String targetPath = "Output.txt";

        int cnt = 0;
        int []arr = new int[100];
        int [][]dp = new int[100][100]; // dp数组 表示从i到j最少的费用
        int [][]path = new int[100][100]; // 记录中间停靠站点k值的二维数组
        int []pathPoint = new int[100]; int pathIndex = 0; // 记录停靠的站点


        try {
            Scanner scanner = new Scanner(new FileReader("Input.txt"));
            while(scanner.hasNextInt()) {
                arr[cnt++] = scanner.nextInt();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        int n = arr[0]; // 站点总数
        cnt = 0;

        for(int i = 1 ; i <= n ; i++) { // dp数组初始化 上三角二维数组
            for(int j = i + 1; j <= n ; j++) {
                dp[i][j] = arr[++cnt];
            }
        }

        System.out.println("最少的花费为:" + minFare(n,dp,path));

        minPath(1,n,path,pathPoint,pathIndex);
        System.out.printf("经过的站点为:1 ");
        printPath(pathPoint);
        System.out.print(n);

    }

    public static int minFare(int n ,int [][]dp , int [][]path) {
        for(int d = 3; d <= n ; d++) { // 1. 区间长度
            for (int i = 1 ; i <= n - d + 1; i++) { // 2. 起点的取值范围
                int j = i + d - 1; // 3. 起点对应的终点的值
                for(int k = i + 1 ; k < j ; k++) { // 4. 可能停靠的站点
//                  dp[i][j] = Math.min(dp[i][j],dp[i][k] + dp[k][j]);
                    if(dp[i][k] + dp[k][j] < dp[i][j]) {
                        dp[i][j] = dp[i][k] + dp[k][j];
                        path[i][j] = k;
                    }
                }
            }
        }
        return dp[1][n];
    }

	// 递归求出可能停靠的站点
    public static void minPath(int i , int j , int [][]path, int []pathPoint,int pathIndex) {
        if(path[i][j] == 0){}
        else {
            pathPoint[pathIndex++] = path[i][j];
            minPath(i,path[i][j],path,pathPoint,pathIndex);
            minPath(path[i][j],j,path,pathPoint,pathIndex);
        }
    }

	// 打印出最少花费的路径
    public static void printPath(int []pathPoint) {
        int cnt = 0;
        while(pathPoint[cnt++] != 0);
        int []arr = new int[cnt-1];
        for(int i = 0 ; i < cnt-1 ; i++) arr[i] = pathPoint[i];
        Arrays.sort(arr);
        for(int i = 0 ; i < cnt-1 ; i++) System.out.printf(arr[i] + " ");
    }

}

注意:把文件中的内容按照int数字读出来的方法

Scanner scanner = new Scanner(new FileReader("Input.txt"));
            while(scanner.hasNextInt()) {
                arr[cnt++] = scanner.nextInt();
            }

在找到此文件读取方法之前,笔者一直在用一整行读取或者是按照1个字节读取,在这个地方耽误了很久时间。

实际上无论是一整行还是1个字节,对于上三角存放文件中的数组都不方便!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值