[区间动态规划] 棋盘分割

题目描述

​ 将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n−1)次后,连同最后剩下的矩形棋盘共有 n 块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)

​ 原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割n块矩形棋盘,并使各矩形棋盘总分的平方和最小。

​ 请编程对给出的棋盘及 n,求出平方和的最小值。


输入

​ 第1行为一个整数n(1<n<15)。

​ 第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。

输出

​ 仅一个数,为最小的平方和值。

输入样例1
3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3
输出样例1
1460

数据规模与限定

时间限制:1 s

内存限制:64 M

解题分析

理解题意后,我们发现,我们每次只能横着切或者竖着切,并且切割下来的棋盘不能再次进行切割,然后题目要求我们去计算所有格子的分值之和的平方,这就考验了我们一个重要的思想,前缀和思想,如果我们每次都去一个一个地加和,那么时间必然消耗很多,所以这里考验我们会使用的第一个小技巧就是前缀和思想。我们不妨设定一个Val[i][j]的二维数组,表示从(1,1)到(i,j)的全部棋盘上的数值之和,根据这个定义,我们很快就可以得知,如果要求(i,j)到(k,l)区间上所有的格子分值总和的公式是Val[i][j]-Val[i][l-1]-Val[k-1][j]+Val[k-1][l-1]。这个公式其实不难理解,画个图就好理解了,我们首先减去(1,1)到(i,l-1)区间的数值和,在减去(1,1)到(k-1,j)的数值和 由于它们的交叉重叠部分Val[k-1][l-1]被我们减去了两次,所以最后我们加上这个值就好了。那我们如何创建一个前缀和数组Val[i][j]呢?首先,我们先读入当前(i,j)位置的值,然后再让,Val[i][j]+=Val[i][j-1]+Val[i-1][j]-Val[i-1][j-1] 所以说,为了防止数组越界和保证正确的计算读入,我们最好从i=1,j=1开始读入数据。

完成准备工作以后,再来审视一下这个题目,可以发现,我们要让棋盘剩下n块,就必须切割棋盘n-1次,那问题就来了,我们如何去思考这一过程的dp数组的更新和决策?又如何把它和一大堆子问题联系在一起呢?其实,第一刀切割的位置是有限的(横着切,m行有m-1种切法,竖着切,p行有p-1种切法),所以说,我们枚举第一刀切割的位置即可,切下的这一大块就是n块中的一块,所以设定一个函数f(i,j,k,l,num)表示切割(i,j)到(k,l)位置,使得棋盘变成num块的最小平方和值。边界条件就是num=1的时候,这个时候直接返回棋盘的平方和的值就好了,所以一个记忆搜索法的程序就出来了。

代码实现1
#include <iostream>
using namespace std;

int n,board[9][9]={0};
int dp[10][10][10][10][16]={0};

int V(int k,int l,int i,int j){
	return board[i][j]-board[i][l-1]-board[k-1][j]+board[k-1][l-1];
}

int S(int x){
	return x*x;
}

int f(int i,int j,int k,int l,int p){
	if(p==1){
		return S(V(i,j,k,l));
	}
    if(dp[i][j][k][l][p]) return dp[i][j][k][l][p];
	int ans=1e9,val1,val2;
	for(int m=j;m<l;m++){
		val1=f(i,j,k,m,1)+f(i,m+1,k,l,p-1);
		val2=f(i,j,k,m,p-1)+f(i,m+1,k,l,1);
		ans=min(ans,min(val1,val2));
	}
	for(int m=i;m<k;m++){
		val1=f(i,j,m,l,1)+f(m+1,j,k,l,p-1);
		val2=f(i,j,m,l,p-1)+f(m+1,j,k,l,1);
		ans=min(ans,min(val1,val2));
	}
	dp[i][j][k][l][p]=ans;
	return ans;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++){
			scanf("%d",&board[i][j]);
			board[i][j] += board[i-1][j] + board[i][j-1] - board[i-1][j-1];
		}
	}
	int result=f(1,1,8,8,n);
	printf("%d\n",result);
	return 0;
}

当然啦,直接用for循环打表也是一个很好的选择,而且不太容易爆栈空间。

首先,程序使用一个二维数组val来存储棋盘格子的分值,并使用前缀和的方式计算出每个格子左上方区域的分值和。这样可以在后续计算中快速获取任意矩形棋盘的总分。

然后,程序使用一个五维数组dp来动态规划地计算平方和的最小值。数组dp[t][k][l][i][j]表示将棋盘分割为t块矩形棋盘时,在区域(k,l)(i,j)之间的平方和的最小值。

接下来,程序使用嵌套的循环遍历所有可能的矩形棋盘区域,并计算出对应的平方和。初始状态下,当t=1时,直接计算出每个矩形棋盘的平方和。

然后,程序通过动态规划的方式计算出将棋盘分割为t块矩形棋盘时的最小平方和。对于每个t,通过遍历所有可能的切割位置,将棋盘切割为两个子区域,然后使用之前计算得到的结果dp[1][k][l][i][c]dp[t-1][k][c+1][i][j](或dp[t-1][k][l][i][c]dp[1][k][c+1][i][j])计算出当前状态的最小平方和。通过遍历所有切割位置,找到最小的平方和,并将结果存储在dp[t][k][l][i][j]中。

最后,程序输出dp[n][1][1][8][8],即将整个棋盘分割为n块矩形棋盘时的最小平方和。

这种动态规划的解决方法可以有效地解决棋盘分割问题,并找到平方和的最小值。

代码实现2
#include <iostream>
using namespace std;

int val[9][9],n,dp[16][10][10][10][10]={0};

int V(int k,int l,int i,int j){
	return val[i][j]-val[i][l-1]-val[k-1][j]+val[k-1][l-1];
}

int S(int x){
	return x*x;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=8;i++)
		for(int j=1;j<=8;j++){
			scanf("%d",&val[i][j]);
			val[i][j]+=val[i-1][j]+val[i][j-1]-val[i-1][j-1];
		}
	for(int i=1;i<=8;i++)
		for(int j=1;j<=8;j++)
			for(int k=i;k<=8;k++)
				for(int l=j;l<=8;l++){
					dp[1][i][j][k][l]=S(V(i,j,k,l));
				}
	for(int t=2;t<=n;t++)
		for(int k=1;k<=8;k++)
			for(int l=1;l<=8;l++)
				for(int i=k;i<=8;i++)
					for(int j=l;j<=8;j++){
						int ans=1e9;
						for(int c=l;c<j;c++){
							int val1=dp[1][k][l][i][c]+dp[t-1][k][c+1][i][j];
							int val2=dp[t-1][k][l][i][c]+dp[1][k][c+1][i][j];
							ans=min(ans,min(val1,val2));
						}
						for(int c=k;c<i;c++){
							int val1=dp[1][k][l][c][j]+dp[t-1][c+1][l][i][j];
							int val2=dp[t-1][k][l][c][j]+dp[1][c+1][l][i][j];
							ans=min(ans,min(val1,val2));
						}
						dp[t][k][l][i][j]=ans;
					}
	printf("%d\n",dp[n][1][1][8][8]);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/284075.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot】SwaggerKnif4j接口文档集成

[TOC] 序&#xff1a;接口文档 ​ 在开发过程中&#xff0c;接口文档是非常重要的一环&#xff0c;在 Spring Boot 中&#xff0c;我们可以通过集成第三方来实现接口文档的自动生成。 ​ 通过注解来描述接口&#xff0c;然后根据这些注解自动生成接口文档&#xff0c;它不仅…

在Mac上恢复SD卡数据的 6 个有效应用程序

慌&#xff01;SD卡里的照片和视频不小心删了&#xff0c;Mac设备上还恢复不了数据&#xff01; 遇到这种情况&#xff0c;你需要的是一款可靠的Mac适用的SD卡恢复软件。我们为你准备了一份最佳的SD卡恢复软件列表&#xff0c;并且还有详细的评论。另外&#xff0c;我们还会给…

WinForm开发 - C# RadioButton(单选框) 设置默认选中或取消默认选中

WinForm开发中RadioButton组件使用过程中的小技巧。 1、属性界面操作 如果有多个组件&#xff0c;希望不显示默认选中单选框只需要将其Checked属性全部设置为False即可&#xff0c; 如果希望默认多个组件中显示默认选中&#xff0c;将其Checked属性设置为True。 2、代码实…

二分算法--x的平方根

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 二分算法前言 二分算法原理超详细讲解(包括暴力求解&#xff0c;朴素二分查找&#xff0c;二分查找左右端点)&#xff1a;二分查找(非朴素)--在排序数组中查找元素的第一个和最后一个位置https://blog.csdn.net/m0_74824254…

5 个让日常编码更简单的 Python 库

今天我们一起来研究一些非常有用的第三方模块&#xff0c;可以使得我们的日常编码变得更加简单方便 sh https://github.com/amoffat/sh 如果曾经在 Python 中使用过 subprocess 库&#xff0c;那么我们很有可能对它感到失望&#xff0c;它不是最直观的库&#xff0c;可能还有些…

Javascript 循环结构while do while for实例讲解

Javascript 循环结构while do while for实例讲解 目录 Javascript 循环结构while do while for实例讲解 一、while语句 二、do…while语句 三、for循环 疑难解答 我们从上一节课知道&#xff0c;JavaScript循环结构总有3种&#xff1a; &#xff08;1&#xff09;while语…

2024年【黑龙江省安全员C证】考试及黑龙江省安全员C证找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年黑龙江省安全员C证考试为正在备考黑龙江省安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的黑龙江省安全员C证找解析祝您顺利通过黑龙江省安全员C证考试。 1、【多选题】下列属于编制安全检查…

WorkQueue模型

WorkQueues&#xff0c;也被称为任务队列模型。当消息处理比较耗时的时候&#xff0c;可能生产消息的速度会远远大于消息的消费速度。长此以往&#xff0c;消息就会堆积越来越多&#xff0c;无法及时的处理。此时就可以使用work模型&#xff1a;让多个消费者绑定到一个队列&…

动力学约束下的运动规划算法——Kinodynamic RRT*算法

一、RRT * 算法回顾 为了更好的理解Kinodynamic RRT*算法&#xff0c;我们先来回顾一下RRT * 算法 RRT * 先通过Sample函数随机选取一个点Xrand&#xff0c;然后通过Near函数找到当前树上距离Xrand最近的一个点Xnear&#xff0c;再通过Steer函数&#xff0c;沿着从Xnear到Xra…

04 HAL库下使用定时器产生一个中断

目录 一、定时器的相关知识点 1.定时器的定义 2. 查看时钟配置 3. 定时器的分类 二、实验开始 1. 配置一个定时器 2.打开定时器的中断配置 引言 在本文的开头我想给大家分享一下单片机工作的两种工作模式轮询和中断&#xff08;异步&#xff09;&#xff0c; 中断也叫做…

一文搞懂什么是缓存穿透、缓存雪崩、缓存击穿三个概念,以及解决方案

先理解概念&#xff1a;【注&#xff1a;我们这里说的是分布式、高并发环境】 一、缓存穿透是什么&#xff1f; 缓存穿透是指&#xff1a;请求【可以有很多】的数据在缓存、关系型数据库中都不存在&#xff0c;每次来查询都会查询到关系型数据库中。 解决方案&#xff1a; 1、将…

打破数据孤岛:ChatGPT如何打通金融大数据的任督二脉?

文章目录 一、引言二、ChatGPT与金融大数据分析的融合三、实践应用&#xff1a;ChatGPT在金融大数据分析中的优势与挑战四、案例分析&#xff1a;ChatGPT在金融大数据分析中的应用案例五、前景展望&#xff1a;ChatGPT在金融大数据分析领域的未来发展《AI时代Python金融大数据分…

Qt5 安装教程 - 跳过登录界面

Qt5 安装教程 - 跳过登录界面 引言一、下载二、安装三、使用四、修改、维护、卸载 引言 Qt5.14.2及以前的版本有离线安装包&#xff0c;无需登录 (老版本连登录界面也无)。之后的版本需登录进行在线安装。 本文以Qt5.12.2版本为例&#xff0c;说明如何跳过登录界面&#xff0c…

如何解决企业内部FTP文件传输速度过慢和安全问题

在数据化时代里&#xff0c;企业内部的文件传输永远是刚需&#xff0c;而因为 FTP协议的简单、易用、广泛支持等优点&#xff0c;让很多企业早期都普遍使用&#xff0c;随着数量量的增多&#xff0c;和对安全的要求越来越高&#xff0c;FTP也暴露出了一些列问题&#xff0c;小编…

算法逆袭之路(1)

11.29 开始跟进算法题进度! 每天刷4题左右 ,一周之内一定要是统一类型 而且一定稍作总结, 了解他们的内在思路究竟是怎样的!! 12.24 一定要每天早中晚都要复习一下 早中午每段一两道, 而且一定要是同一个类型, 不然刷起来都没有意义 12.26/27&#xff1a; 斐波那契数 爬…

使用 Tkinter 制作一个进制转换工具,好用!

在平时工作学习当中&#xff0c;我们经常会编写一些简单的 Python GUI 工具&#xff0c;以此来完成各种各样的自动化任务&#xff0c;比如批量处理文件&#xff0c;批量处理图片等等。当我们进行这些工具的编写之时&#xff0c;往往只关注了功能的实现&#xff0c;而忽略了页面…

Android 跨进程之间通信(IPC)方式之ContentProvider

Android 跨进程之间通信 Android 跨进程之间通信(IPC)方式之BroadcastReceiverAndroid 跨进程之间通信(IPC)方式之ContentProvider 文章目录 Android 跨进程之间通信前言一、ContentProvider 是什么&#xff1f;二、如何利用ContentProvider跨进程通信1.创建自定义ContentProv…

伺服电机:原点复位

一、原点复位概念 原点复位指的是&#xff0c;在驱动器使能时&#xff0c;触发原点复位功能后&#xff0c;电机将主动查找零点&#xff0c;完成定位功能。 那么问题来了&#xff0c;什么是原点&#xff0c;什么是零点&#xff1f; 原点&#xff1a;即机械原点&#xff0c;可…

面向对象知识点

类和对象知识点梳理 1. 类和对象的概念 类是对一类事物的描述&#xff0c;是抽象的、概念上的定义。Java 中定义类的关键字是&#xff1a;class。 具有相同特征和行为的对象抽象成类&#xff0c;类描述了这一类对象的属性和方法&#xff1a; 属性&#xff08;成员变量&#x…

分布式技术之数据复制技术

文章目录 什么是数据复制技术&#xff1f;数据复制技术原理及应用同步复制技术原理及应用异步复制技术原理及应用半同步复制技术原理及应用三种数据复制技术对比 什么是数据复制技术&#xff1f; 数据复制是一种实现数据备份的技术。数据复制技术&#xff0c;可以保证存储在不…