当前位置: 首页 > news >正文

区间DP————石子合并

区间DP————石子合并

  1. 合并石头的最低成本
    力扣:https://leetcode-cn.com/problems/minimum-cost-to-merge-stones/
    有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头。
    每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的总数。
    找出把所有石头合并成一堆的最低成本。如果不可能,返回 -1 。

示例 1:

输入:stones = [3,2,4,1], K = 2
输出:20
解释:
从 [3, 2, 4, 1] 开始。
合并 [3, 2],成本为 5,剩下 [5, 4, 1]。
合并 [4, 1],成本为 5,剩下 [5, 5]。
合并 [5, 5],成本为 10,剩下 [10]。
总成本 20,这是可能的最小值。
示例 2:

输入:stones = [3,2,4,1], K = 3
输出:-1
解释:任何合并操作后,都会剩下 2 堆,我们无法再进行合并。所以这项任务是不可能完成的。.
示例 3:

输入:stones = [3,5,1,2,6], K = 3
输出:25
解释:
从 [3, 5, 1, 2, 6] 开始。
合并 [5, 1, 2],成本为 8,剩下 [3, 8, 6]。
合并 [3, 8, 6],成本为 17,剩下 [17]。
总成本 25,这是可能的最小值。

提示:

1 <= stones.length <= 30
2 <= K <= 30
1 <= stones[i] <= 100

首先对于区间DP的一般步骤:
1、枚举所有可能的区间长度
2、枚举所有的左端点
3、枚举所有的区间分解点
4、状态转移

for( int len = 2 ; len <= n ; len++ )
{
	for( int i = 1 ; i + len - 1 <= n ; i++ )
	{
		int j = i + len - 1;
		for( int mid = i ; mid <= j ; mid++ )
		{
			//dp[i][j] = f( dp[i][j] )
		}
	}
}

本题我们可以先考虑k = 2的情况。
我们定义f[i][j],f[i][j] 表示将第i堆石子,到 第j 堆石子合并为1堆石子的最小成本,那么接下来我们考虑如何去计算f[i][j]。
由于k = 2,所以在合并任意i 到 j堆石子时,最后一定是将两堆石子合并起来,所有方案唯一的不同无非就是将i 到 j这个区间分为两个区间的分界点是哪个,所以设mid为分界点,则状态转移方程为:
f[i][j] = min( f[i][j] , f[i][mid] + f[mid+1][j] + s[j] - s[i-1] );
s为前缀和,如s[j]表示第一堆石子到第j堆石子的所有成本

那么当k 等于任意常数时该如何解决这个问题呢?

我们由简到繁,先考虑当k 为一个确定值时,什么情况下最终无法合并为一堆石子输出-1?
假设k = 3,我们直接列举可以合并为一堆的情况那就是,3堆,5堆,7堆…不难发现可以合并为一堆的情况之间都相差2,那么很容易得出公式:
当一个区间的长度(石子的堆数) = (k-1)*N + k (N=1,2,3…)时可以将所有石子合并为一堆

接着我们仍然定义状态f[i][j] ,表示将第i堆 到 第j堆石子合并为一堆石子的最小成本,我们还是考虑最后一步,还是假设最后是将两堆石子合并为一堆,
但两堆石子的其中一堆有 1 堆,另一堆有 k - 1 堆,这样他们一共就有k堆了,可以合并为1堆,仍然符合题意,然后这两堆又可以被划分为更小的子问题,所以
f[i][j] = min( f[i][j] , f[i][mid] + f[mid+1][j] );
在加上s[j] - s[i-1]之前要先判断一下i 到 j的区间长度是否满足 k 值

接下来是本题的代码:

#include<stdio.h>
#include<math.h>

int mergeStones(int* stones, int stonesSize, int k)
{
    int n = stonesSize;
    //若不能合并为一堆,直接返回 -1 
    if( (stonesSize-k) % (k-1) != 0 )
        return -1;
    
    int sum[n+1];
    
    //状态表示:f[i][j]:集合:所有将第 i 堆到第 j 堆石子合并为 1 堆石子的合并方法 
	//					 属性:MIN 
	//状态计算:f[i][j] = fmin( f[i][j] , f[i][mid] + f[mid+1][j] );
	//若区间长度符合要求:
	//		    f[i][j] = f[i][j] + sum[j] - sum[i-1];
    int f[n+1][n+1];
	
	//计算前缀和 
    for( int i = 1 ; i <= n ; i++ )
    {
        sum[i] = sum[i-1] + stones[i-1];
    }
	
	//初始化,仅有 f[i][j] = 0 这一个条件
    for( int i = 1 ; i <= n ; i++ )
    {
        f[i][i] = 0;
    }
	
    for( int len = 2 ; len <= n ; len++ )//枚举 区间长度 
    {
    	printf("\nlen = %d\n",len);
        for( int i = 1 ; i + len - 1 <= n ; i++ )//枚举 左端点 
        {
            int j = i + len - 1;
            f[i][j] = 1e8;//初始化初始状态为最大值,方便后续比较 
            
			//在i,j之间移动 mid ,每次移动 K-1 个距离,dp(i,mid) + dp(mid+1,j) 代表以mid为界限合并为两队的成本,取最小值 
			for( int mid = i ; mid < j ; mid += k-1 )//遍历 所有 可能的 区间分解点 
            {
                f[i][j] = fmin( f[i][j] , f[i][mid] + f[mid+1][j] );
                printf("\n判断前:i = %d ,j = %d ,f[i][j] = %d\n",i,j,f[i][j]);
            }
            if( (len-k) % (k-1) == 0 )//若区间长度可以合并,则给f[i][j]加上区间内的成本 
            {
                f[i][j] = f[i][j] + sum[j] - sum[i-1];
                printf("判断后:i = %d ,j = %d ,f[i][j] = %d\n",i,j,f[i][j]);
            }
        }
    }
    return f[1][n];//返回合并第 i 堆到第 j 堆石子的最小成本 
}

int main()
{
	int n,k;
	scanf("%d %d",&n,&k);
	
	int stones[n];
	
	for( int i = 0 ; i < n ; i++ )
	{
		scanf("%d",&stones[i]);
	}
	
	mergeStones( stones , n , k );
}

相关文章:

  • C/C++无穷大的表示 0x7fffffff + 0x7fffffff= 负数
  • 李永乐(一)行列式计算——笔记
  • 李永乐(二)矩阵的概念及运算——笔记
  • C++——using namespace std; 解析
  • 李永乐(三)伴随矩阵、可逆矩阵——笔记
  • 操作系统——考试复习
  • 李永乐(四)初等变换、初等矩阵、分块矩阵——笔记
  • 李永乐(五)向量、线性表出——笔记
  • 李永乐(六)线性相关——笔记
  • 李永乐(七)向量组的秩、矩阵的秩——笔记
  • 李永乐(八)齐次线性方程组——笔记
  • 计算机网络微课堂——第一、二章—概述、物理层
  • 朴素版Dijkstra(最短路径)算法
  • 数据库—应付考试—期末复习
  • 大物---期末复习
  • 03Go 类型总结
  • Android Studio:GIT提交项目到远程仓库
  • CEF与代理
  •  D - 粉碎叛乱F - 其他起义
  • eclipse的离线汉化
  • es6--symbol
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • JavaScript设计模式与开发实践系列之策略模式
  • Java多线程(4):使用线程池执行定时任务
  • Laravel Telescope:优雅的应用调试工具
  • PHP 小技巧
  • Python十分钟制作属于你自己的个性logo
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 闭包--闭包之tab栏切换(四)
  • 基于axios的vue插件,让http请求更简单
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 前端之Sass/Scss实战笔记
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (11)MATLAB PCA+SVM 人脸识别
  • (python)数据结构---字典
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (算法)前K大的和
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (转载)hibernate缓存
  • .dwp和.webpart的区别
  • .Net 6.0 处理跨域的方式
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .net 微服务 服务保护 自动重试 Polly
  • .net 中viewstate的原理和使用
  • .Net下的签名与混淆
  • [ C++ ] STL_list 使用及其模拟实现
  • [AIGC] SQL中的数据添加和操作:数据类型介绍
  • [Android 13]Input系列--获取触摸窗口
  • [C#]使用PaddleInference图片旋转四种角度检测
  • [c++] 单例模式 + cyberrt TimingWheel 单例分析