【洛谷】P5752 [NOI1999] 棋盘分割(记忆化搜索)

题意

给定一个 \(8*8\) 的棋盘,棋盘上的每一个位置都有一个数。将棋盘水平或竖直切一刀,将剩下的一部分继续分割(也就是不能在两个子棋盘里面同时分割),直至棋盘被分成 \(n\) 个部分,每一个部分的分值为其中的数字之和。求出最小的均方差值。

均方差 \(\sigma=\sqrt{\dfrac{ {\textstyle \sum_{i=1}^{n}} (x_i-\overline{{x}} )^2\ }{n} }\) ,其中 \(\overline{x}=\dfrac{ {\textstyle \sum_{i=1}^{n}}x_i }{n} ,x_i\) 表示第 \(i\) 个棋盘内的数字之和。

思路

首先可以发现对于 \(\overline{x}\) 的计算,\({\textstyle \sum_{i=1}^{n}}x_i\) 一定为一个定值,也就是棋盘中所有数之和。也就是说,\(\overline{x}\) 的值是确定的,可以先预处理出来。

定义 \(s[i][j]\) 表示左上角为 \((1,1)\) 右下角为 \((i,j)\) 的棋盘中的数之和,也就是二维前缀和,这样就可以先预处理出一遍 \(s\) 数组,之后就可以在 \(O(1)\) 的时间内求出一个子棋盘的数字和。

定义 \(f[x1][y1][x2][y2][k]\) 表示将左上角坐标为 \((x1,y1)\) ,右下角坐标为 \((x2,y2)\) 的子棋盘分割成 \(k\) 个棋盘的最小值。这里的最小值定义为 \(min(\dfrac{\sum (x_i-\overline{x} )^2}{n} )\) ,最终只要在输出答案的时候开平方就可以了。

考虑状态转移方程。这个比较简单。可以枚举分割线的位置,同时为了满足题目中不能同时分割本次分割后的两个子棋盘。所以一定是一边继续分割,另一边只能当一个棋盘,即:

\(f[x1][y1][x2][y2][k]=min(f[x1][y1][i][y2][k-1]+get(i+1,y1,x2,y2),f[i+1][y1][x2][y2][k-1]+get(x1,y1,i,y2))\) \((x1 \leq i \leq x2-1)\)

其中 \(get\) 函数用来求出一个子棋盘的 \(\dfrac {(x-\overline{x})^2}{n}\)
这里给出的是枚举竖直方向分割棋盘,水平方向分割与此同理。

蒟蒻用的是记忆化搜索,因为比较好处理出子矩阵的值,同时也可以减少码量(
其实就是懒)。

最终别忘了特判 \(k=1\) 的情况,只要返回 \(get\) 函数就行了。

code:

#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=20;
const int M=10;
const double INF=1e18;
double s[M][M],X,f[M][M][M][M][N],n;
double min(double a,double b){return a<b?a:b;}
double NLC(int x1,int y1,int x2,int y2)
{
	double sum=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]-X;
	return (double)sum*sum/n;
}
double dfs(int x1,int y1,int x2,int y2,int k)
{
	double &now=f[x1][y1][x2][y2][k]; //用一个值来代表f[x1][y1][x2][y2][k]比较方便
	if(now>=0) return now;
	if(k==1) return now=NLC(x1,y1,x2,y2);
	now=INF;
	for(int i=x1;i<x2;i++)
	{
		now=min(now,dfs(x1,y1,i,y2,k-1)+NLC(i+1,y1,x2,y2));
		now=min(now,NLC(x1,y1,i,y2)+dfs(i+1,y1,x2,y2,k-1));
	}
	for(int j=y1;j<y2;j++)
	{
		now=min(now,dfs(x1,y1,x2,j,k-1)+NLC(x1,j+1,x2,y2));
		now=min(now,NLC(x1,y1,x2,j)+dfs(x1,j+1,x2,y2,k-1));
	}
	return now;
}
int main()
{
	scanf("%lf",&n);
	for(int i=1;i<=8;i++)
		for(int j=1;j<=8;j++)
	        {
	        	scanf("%lf",&s[i][j]);
	        	s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
			} 
	memset(f,-1,sizeof(f));// double类型数组,如果memset中写1会赋值成一个很奇怪的数值 
	X=s[8][8]/n; 
	printf("%.3lf\n",sqrt(dfs(1,1,8,8,n)));
	return 0;
}

双倍经验。(这道蓝题其实更水。。)

posted @ 2021-06-04 11:31  曙诚  阅读(126)  评论(0)    收藏  举报