蓝桥杯-和并石子--区间DP

问题描述
  在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。
输入格式
  输入第一行包含一个整数n,表示石子的堆数。
  接下来一行,包含n个整数,按顺序给出每堆石子的大小 。
输出格式
  输出一个整数,表示合并的最小花费。
样例输入
5
1 2 3 4 5
样例输出
33
数据规模和约定

  1<=n<=1000, 每堆石子至少1颗,最多10000颗。


假如有5堆石头:7 6 5 7 30;

最后合成一堆的石头,肯定是由两堆石头合起来的;a,b两坨;a这坨可能是由原来的1堆,2堆,3堆,4堆合起来的;b坨对应的就是4,3,2,1坨。。

我们需要把这些情况都算出来:并取这四中情况最小的作为选择;

1+4;2+3;3+2;4+1;因为是相邻才能合并;所以1+4和4+1并不是一种情况;可以把1+4理解为a是第一堆,b是第二堆到第五堆。。

怎么算呢?假如是a=1;b=4这种情况;a=1,就是第一坨石头数量嘛:7;b坨又可以分为两坨c,d:1+3;2+2;3+1;

这些情况也需要算出来。。并选出这其中花费最小的组合;

怎么算。。假如c,d是1+3组合;这里c=1;自然是第二堆石头:6;d坨又可能分为e,f:1+2;2+1;算出这两种情况,并选出花费最小的组合;

怎么算!假如e,f为1+2的组合;e为1,即第三堆:5;f为2,自然为1+1;分别为第四堆和第五堆数量了;


用dp[i][j]记录从第i堆合并到第j堆的最小花费

所以需要从底层往上走;先算出两个两个一堆的dp[0][1],dp[1][2],dp[2][3],dp[3][4];

这样才能算三个三个一堆的:dp[0][2]=Min{dp[0][0]+dp[1][2] , dp[0][1],dp[2][2]}; 依次dp[1][3],dp[2][4];

算四个四个同一堆的:dp[0][3]=Min{dp[0][0]+dp[1][3] , dp[0][1]+dp[2][3] , dp[0][2] + dp[3][3]};依次dp[1][4];

最后算5个一堆的!dp[0][4] = Min{dp[0][0]+dp[1][4] , dp[0][1]+dp[2][4] , dp[0][2]+dp[3][4] , dp[0][3]+dp[4][4]};

你看最后的dp[0][4] 是不是就是 1+4;2+3;3+2;4+1;这四种组合选出最小的意思~

状态转移方程:

dp[i][j] = Min{dp[i][k] + dp[k+1][j]} + sum[i][j];(i!=j)(k从i到j-1);

其中sum[i][j]为i到j的石头总量;为啥还有这个?你看状态里不有两堆石头嘛,dp[i][k] ,dp[k+1][j] 这两堆石头合并还要花费啊。



import java.util.Scanner;

public class 提高算法合并石子DP {
	static int sum;
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		int stone[] = new int[n];
		int dp[][] = new int[n][n];//dp[i][j]储存从i到j的石头合并需要的最少花费
		for (int i = 0; i < n; i++) {
			if (i==0) {
				stone[i] = input.nextInt();
			}else {
				stone[i] = stone[i-1] + input.nextInt();
			}
		}
		for (int i = 0; i < n; i++) {
			dp[i][i] = 0;//从i到i的石头合并花费为0
		}
		for (int l = 2;  l <= n; l++) {//遍历每种长度l从2->n
			for (int start = 0; start < n-l+1; start++) {//遍历l长度下的每个起点
				int end = start + l - 1;//对于此时的start对应的终点为end
				dp[start][end] = Integer.MAX_VALUE;
				for (int k = start; k < end; k++) {//对于dp[start][end]按k分割,有K中分割方案;
					if (dp[start][end]>dp[start][k]+dp[k+1][end]) {
						dp[start][end]=dp[start][k]+dp[k+1][end];//依次比较,取最小的方案;
					}
				}
				//上面部分是分割了两部分;到这了dp[start][end]等于分割到两部分的花费,还需要加上把这两部分合并的花费
				//也就是从start 到end 的石头数量
				int sum = 0;
				if (start==0) {
					sum = stone[end];
				}else {
					sum = stone[end] - stone[start-1];
				}
				dp[start][end] += sum;//加上从start到end的石头数量
			}
		}
		System.out.println(dp[0][n-1]);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

obession

觉得有用可以打赏咖啡一杯~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值