[NOI1999] 棋盘分割

【NOI1999】 棋盘分割

题目描述

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

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

均方差 \(\sigma=\sqrt{\dfrac{\sum^{n}_{i=1}\times(x_i-\bar x)^2}{n}}\),其中平均值 \(\bar x=\dfrac{\sum_{i=1}^{n}x_i}{n}\),\(x_i\)​ 为第 \(i\) 块矩形棋盘的分。

请编程对给出的棋盘及 \(n\),求出 \(\sigma\) 的最小值。

输入格式

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

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

输出格式

仅一个数,为 \(\sigma\) (四舍五入精确到小数点后三位)。

题目分析

终于从 y 总那里听懂了,于是赶快来写篇题解。

一道区间 DP。

设平均值 \(\bar x = \dfrac{\sum_{i=1}^{n}x_i}{n}\),

方差 \(\sigma = \sqrt{\dfrac{\sum_{i=1}^{n}\times (x_i-\bar x)^2}{n}}\)

由于我们要使方差 \(\sigma\) 最小,那么不妨将方差平方后再进行化简(过程我会写的有些繁琐,可以跳着看):

\(\sigma^2=\dfrac{\sum_{i=1}^{n}\times (x_i-\bar x)^2}{n}\)

\(\sigma^2=\dfrac{1}{n}\times (\sum_{i=1}^{n}\times({x_i}^2 +{\bar x}^2 - 2\times x_i\times \bar x))\)

\(\sigma^2 = \frac{1}{n}(\sum_{i=1}^n{x_i}^2 - 2\sum_{i=1}^nx_i\times\bar{x} + \sum_{i=1}^n\bar{x}^2)\)

\(\sigma^2 = \frac{1}{n}\sum_{i=1}^nx_i^2 - 2\bar{x}\times\frac{1}{n}\sum_{i=1}^nx_i + \frac{1}{n}\times n\bar{x}^2\)

发现第二项的 \(\frac{1}{n}\sum_{i=1}^nx_i\) 正好就是 \(\bar x\),于是:

\(\sigma^2 = \frac{1}{n}\sum_{i=1}^nx_i^2 - 2\overline{x}^2 + \bar{x}^2\)

\(\sigma^2 = \frac{1}{n}\sum_{i=1}^nx_i^2 - \bar{x}^2\)

\(\bar x\) 很明显是一个可以马上求出的数,并且我们需要频繁地求矩阵和,所以考虑使用二维前缀和 \(O(1)\) 查询矩阵和,大大加快了效率。

在这篇题解中,我们采用 \((x1,y1,x2,y2)\) 的方式表示一个矩阵,其左上角为 \((x1,y1)\),右下角为 \((x2,y2)\)。

令 \(dp[a][b][c][d][k]\) 表示划分到 \(k-1\) 个的子矩阵,是以 \((a,b)\) 为左上角,\((c,d)\) 为右下角。

我们可以采用记忆化方法来完成此过程。

//2021/8/9

#include <iostream>

#include <cstdio>

#include <cmath>

#include <cstring>

#include <algorithm>

#define debug(c) cerr<<#c<<"="<<c<<endl

using namespace std;

const int ma=10,maxn=16;

const double INF=1e9;

int n;

int sum[ma][ma];

double dp[ma][ma][ma][ma][maxn];

double bar;//平均值 

//如果是C++11可以这样,但是有些编译器因为这里又有cmath又有y1 y2,会CE 
//当然就算报错,换个名就好了
 
inline double getsum(int x1,int y1,int x2,int y2)
{
	double res=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]-bar;
	
	return res*res;
}

inline double dfs(int x1,int y1,int x2,int y2,int k)//记忆化方法 
{
	double &tmp=dp[x1][y1][x2][y2][k];
	
	if(tmp>=0)
	{
		return tmp;
	}
	
	if(k==1)
	{
		return getsum(x1,y1,x2,y2);
	}
	
	tmp=INF;
	
	//横着去切 
	
	for(register int i=x1;i<x2;i++)
	{
		tmp=min(tmp,dfs(x1,y1,i,y2,k-1)+getsum(i+1,y1,x2,y2));
		
		tmp=min(tmp,dfs(i+1,y1,x2,y2,k-1)+getsum(x1,y1,i,y2)); 
	}
	
	//竖着去切 
	
	for(register int i=y1;i<y2;i++)
	{
		tmp=min(tmp,dfs(x1,y1,x2,i,k-1)+getsum(x1,i+1,x2,y2));
		
		tmp=min(tmp,dfs(x1,i+1,x2,y2,k-1)+getsum(x1,y1,x2,i));
	}
	
	return tmp;
}

int main(void)
{
	scanf("%d",&n);
	
	for(register int i=1;i<=8;i++)
	{
		for(register int j=1;j<=8;j++)
		{
			scanf("%d",&sum[i][j]);
			
			sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		}
	}
	
	memset(dp,-1,sizeof(dp));
	
	bar=(double)sum[8][8]/n;
	
	printf("%.3lf\n",sqrt(dfs(1,1,8,8,n)/n));
	
	return 0;
}
上一篇:008 PCI设备BAR空间的初始化


下一篇:训练时间统计