SDNUOJ 1048 石子合并2(区间动态规划)

题目描述

有n堆石子排成一圈,每次选择相邻的两堆石子,将其合并为一堆,记录该次合并的得分为两堆石子个数之和。已知每堆石子的石子个数,求当所有石子合并为一堆时,最小的总得分。
Input
第一行一个整数n(1 <= n <= 200),表示石子堆数; 第二行n个整数a(1 <= a <= 100),表示每堆石子的个数,这些石子首尾相接排成一圈。
Output
一个整数,表示最小总得分。
Sample Input

5
7 6 5 7 100

Sample Output

175

解析

它其实是这道题(石子合并1)的变形。
我们只要改变石子合并1的状态转移方程即可。
概率环形问题的区间问题,存在这样的情况,假设石子堆为4个,即我们的最优区间可能是3,4,1,2。

方法一

这样的情况是线性石子堆无法解决的,因为线性石子堆我们只需考虑1,2,3,4的区间最优即可。所以我们的外循环就不是长度了,而变为开始区间的索引,内循环则为从这个区间开始往后数几个,然后k则为划分这个区间。所以状态转移方程为:

d p [ i ] [ j ] = 0 , j = 1

d p [ i ] [ j ] = m i n { d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ ( i + k 1 ) % n ] [ j k ] + s u m [ i , j ] }

以上方程,只需注意两点:
1. (i + k - 1) \% n 表示的是k划分后,我们的另一个区间的开始的位置,这里我们先要求出第一个区间的末尾位置,取余后再加1,是为了应付这种情况,即假设n = 5, i= 2,k = 3, 如果直接(i+k) % 5 = 0, 显示不对的,第二个区间位置应该是5,所以先减一,再加1
2. sum(i,j), 根据因为此处j指的是区间元素个数, 然后计算区间和。

    static int getSum(int index, int length){
        int sum = 0;
        for(int i = 1; i <= length; i++){
            sum += nums[index];
            index++;
            if(index > n){
                index = 1;
            }
        }
        return sum;
    }

方法一代码

package com.special.SDNUOJ;

import java.util.Arrays;
import java.util.Scanner;

/**
 * Created by Special on 2018/6/4 11:30
 */
public class SDNUOJ1048 {

    static int[] nums;
    static int n;
    static int[][] dp;

    static int getSum(int index, int length){
        int sum = 0;
        for(int i = 1; i <= length; i++){
            sum += nums[index];
            index++;
            if(index > n){
                index = 1;
            }
        }
        return sum;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        while(input.hasNext()){
            n = input.nextInt();
            nums = new int[n + 1];
            dp = new int[n + 1][n + 1];
            for(int i = 1; i <= n; i++) {
                nums[i] = input.nextInt();
                Arrays.fill(dp[i], Integer.MAX_VALUE);
            }
            for(int j = 1; j <= n; j++){
                for(int i = 1; i <= n; i++){
                    int sum = getSum(i, j);
                    dp[i][1] = 0;
                    for(int k = 1; k < j; k++){
                        dp[i][j] = Math.min(dp[i][j],
                                dp[i][k] + dp[(i + k - 1) % n + 1][j - k] + sum);
                    }
                }
            }
            int ans = Integer.MAX_VALUE;
            for(int i = 1; i <= n; i++){
                ans = Math.min(ans, dp[i][n]);
            }
            System.out.println(ans);
        }
    }
}

方法二

既然环形的区间可能从任意位置开始,所以我们可以化环形为直线型问题,问题转为石子合并1问题,转换方法为把当前的石子堆复制一份放到后面,这样就可以了。
为了减少不必要的计算问题,比如n = 4. 我们再求dp[5][6]的最优值,其实它与dp[1][2]是等价,故而我们只需求dp[4][…]的最优区间即可,dp[5+]之后就不用考虑,但是这样在求dp[3][6]最优区间的值时,遇到划分dp[3][4] 和dp[5][6],这时我们就要把dp[5][6]转化为dp[1][2]。因为上面我们只算到打破dp[4]开头的最优区间,后面的就没算。

方法二代码

package com.special.SDNUOJ;

import java.util.Arrays;
import java.util.Scanner;

/**
 * Created by Special on 2018/6/4 11:51
 */
public class SDNUOJ1048_02 {

    static int[] nums, sum;
    static int n;
    static int[][] dp;

    static int getSum(int i, int j){
        int result = 0;
        if(j > n){
            result = sum[j % n] + sum[n] - sum[i - 1];
        }else {
            result = sum[j] - sum[i - 1];
        }
        return result;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        while(input.hasNext()){
            n = input.nextInt();
            dp = new int[2 * n + 1][2 * n + 1];
            nums = new int[2 * n + 1];
            sum = new int[n + 1];
            for(int i = 1; i <= n; i++){
                nums[i] = input.nextInt();
                nums[n + i] = nums[i];
                sum[i] = sum[i - 1] + nums[i];
            }
            for(int i = 1; i <= 2 * n; i++){
                Arrays.fill(dp[i], Integer.MAX_VALUE);
                dp[i][i] = 0;
            }
            for(int len = 1; len < n; len++){
                for(int i = 1; i <= n; i++){
                    int j = i + len;
                    int sum = getSum(i, j);
                    for(int k = i; k < j; k++){
                        dp[i][j] = Math.min(dp[i][j], dp[i][k] +
                                (k >= n ? dp[(k + 1) % n][j % n] : dp[k + 1][j]) + sum);
                    }
                }
            }
            int result = Integer.MAX_VALUE;
            for(int i = 1; i <= n; i++){
                result = Math.min(result, dp[i][i + n - 1]);
            }
            System.out.println(result);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/80567956