不止代码:ybtoj-棋盘分割(二维区间dp)

题目描述

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

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。求出均方差的最小值。
其中,均方差的定义为:
在这里插入图片描述
(里面的括号应该还有一个平方,不然固输0岂不美哉)

数据范围

1<n<15,所有数均为不大于100的非负整数

解析

分析题面可以发现一些结论:
1.只要大矩阵与n确定,平均值就是可以算出的定值
2.每次分完后只能对其中的一部分继续切割
对于第2条举个例子:n=4时,田字型的切割就是不合法的,这对于本题至关重要
我们可以先算出均方差根号里面那个西格玛加和的总值的最小值,最后再除n开根号
定义dp[x1][y1][x2][y2][k]:左上角为x1,y1,右下角为x2,y2还可以切k刀时,切成的k+1部分西格玛方差的最小值
显然,k=0时:

if(k==0) return abs((double)he(x1,y1,x2,y2)-ave)*abs((double)he(x1,y1,x2,y2)-ave);

ave为提前可算好的平均值,he函数可以用二维前缀和O(1)求出,具体见代码(其实遍历求本题时间可能也爆不了。。。
对于转移,我们枚举切出去的那个不能再切的矩形的位置即可

代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
#include <queue>
#include <string>
#include<map>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a));
#define ull unsigned ll
using namespace std;
const int N=15;
int m,n,tot;
int mp[10][10],sum[10][10];
double ave;
double dp[N][N][N][N][N];
int he(int x1,int y1,int x2,int y2){
	return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
double find(int x1,int y1,int x2,int y2,int k){
	if(dp[x1][y1][x2][y2][k]) return dp[x1][y1][x2][y2][k];
	if(k==0) return abs((double)he(x1,y1,x2,y2)-ave)*abs((double)he(x1,y1,x2,y2)-ave);
	dp[x1][y1][x2][y2][k]=1e9;
	for(int i=x1;i<x2;i++){//横着切 
		dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,i,y2,0)+find(i+1,y1,x2,y2,k-1));
		dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,i,y2,k-1)+find(i+1,y1,x2,y2,0));
		//注意这里还要反着枚举一遍,因为不能切的部分可能在下边 
	}
	for(int j=y1;j<y2;j++){//竖着切 
		dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,x2,j,0)+find(x1,j+1,x2,y2,k-1));
		dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,x2,j,k-1)+find(x1,j+1,x2,y2,0));
	}
	return dp[x1][y1][x2][y2][k];
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++) scanf("%d",&mp[i][j]);
	}
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++) sum[i][j]=sum[i][j-1]+mp[i][j];
	}
	for(int j=1;j<=8;j++){
		for(int i=1;i<=8;i++) sum[i][j]+=sum[i-1][j];
	}
	ave=1.0*sum[8][8]/n;
	printf("%.3lf",sqrt(1.0*find(1,1,8,8,n-1)/n));
}
/*
样例: 
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


ans:1.633
*/

心得

一次AC万岁!!!!

这道dp做的还是不错的,尤其是很快发现了解析中提到的两条关键结论,从而带出了思路
所以要仔细审题!!
dp就像数学平几,有时候思路一下子通了就不难了awa

thanks for reading!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值