题目传送门:http://poj.org/problem?id=1191
Description
将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)
原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。
均方差
σ
=
Σ
i
=
1
n
(
x
i
−
x
ˉ
)
2
n
σ=\sqrt \frac{Σ_{i=1}^n(x_i-\bar x)^2}{n}
σ=nΣi=1n(xi−xˉ)2,其中平均值
x
ˉ
=
Σ
i
=
1
n
x
i
n
\bar x=\frac{Σ_{i=1}^nx_i}{n}
xˉ=nΣi=1nxi,
x
i
x_i
xi为第i块矩形棋盘的总分。
请编程对给出的棋盘及n,求出
σ
σ
σ 的最小值。
Input
第1行为一个整数n(1 < n < 15)。
第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。
Output
仅一个数,为 σ σ σ(四舍五入精确到小数点后三位)。
Sample Input
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
Sample Output
1.633
Source
Noi 99
1、先分析协方差的大小与什么有关
将协方差公式展开:
σ = Σ i = 1 n ( x i − x ˉ ) 2 n = Σ i = 1 n x i 2 − 2 Σ i = 1 n x i x ˉ + n x ˉ 2 n σ=\sqrt \frac{Σ_{i=1}^n(x_i-\bar x)^2}{n}=\sqrt \frac{Σ_{i=1}^nx_i^2-2Σ_{i=1}^nx_i\bar x+n\bar x^2}{n} σ=nΣi=1n(xi−xˉ)2=nΣi=1nxi2−2Σi=1nxixˉ+nxˉ2
因为均值公式为:
x ˉ = Σ i = 1 n x i n \bar x=\frac{Σ_{i=1}^nx_i}{n} xˉ=nΣi=1nxi
将均值公式代入协方差公式,有:
σ = Σ i = 1 n x i 2 n − 2 x ˉ 2 + x ˉ 2 = Σ i = 1 n x i 2 n − x ˉ 2 σ=\sqrt{\frac{Σ_{i=1}^nx_i^2}{n} -2\bar x^2+\bar x^2}=\sqrt{\frac{Σ_{i=1}^nx_i^2}{n} -\bar x^2} σ=nΣi=1nxi2−2xˉ2+xˉ2=nΣi=1nxi2−xˉ2
因为均值 x ˉ \bar x xˉ 已经确定,所以协方差只和 Σ i = 1 n x i 2 Σ_{i=1}^nx_i^2 Σi=1nxi2 有关
2、关于记忆化搜索的状态表示
用5个变量表示状态:可以用左上角和右下角两个格子的坐标来表示整个区域,形如:(x1, y1, x2, y2),还需要使用一个变量 k 来表示需要切 k 刀
dfs(k, x1, y1, x2, y2):表示对当前区域切 k 刀后得到的最小平方和
sum(x1, y1, x2, y2):表示当前区域分数的平方
因为题目要求对当前区域进行切割后,要在切出的二个区域中选一个继续切,另一个区域舍弃
所以状态转移方程为(以横切为例):
dfs(k, x1, y1, x2, y2) = min(dfs(k - 1, x1, y1, i, y2) + sum(i + 1, y1, x2, y2), dfs(k - 1, i + 1, y1, x2, y2) + sum(x1, y1, i, y2))
纵切同理:
dfs(k, x1, y1, x2, y2) = min(dfs(k - 1, x1, y1, x2, i) + sum(x1, i + 1, x2, y2), dfs(k - 1, x1, i + 1, x2, y2) + sum(x1, y1, x2, i))
参考的题解链接:https://www.cnblogs.com/handsomecui/p/5207512.html
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 10;
double arr[N][N]; // 前缀和数组
double dp[16][N][N][N][N];
// 当前矩形分数的平方
double sum(int x1, int y1, int x2, int y2)
{
// 二维前缀和
double x = arr[x2][y2] - arr[x1-1][y2] - arr[x2][y1-1] + arr[x1-1][y1-1];
return x * x;
}
// k 表示还需要切几刀
double dfs(int k, int x1, int y1, int x2, int y2)
{
// 当前状态已经计算过
if (dp[k][x1][y1][x2][y2] >= 0)
return dp[k][x1][y1][x2][y2];
// 不需要再切
if (k == 0)
return dp[k][x1][y1][x2][y2] = sum(x1, y1, x2, y2);
double res = 0x3f3f3f3f;
// 枚举横切的位置
for (int i = x1; i < x2; i++) {
res = min(res, dfs(k - 1, x1, y1, i, y2) + sum(i + 1, y1, x2, y2));
res = min(res, dfs(k - 1, i + 1, y1, x2, y2) + sum(x1, y1, i, y2));
}
// 枚举纵切的位置
for (int i = y1; i < y2; i++) {
res = min(res, dfs(k - 1, x1, y1, x2, i) + sum(x1, i + 1, x2, y2));
res = min(res, dfs(k - 1, x1, i + 1, x2, y2) + sum(x1, y1, x2, i));
}
return dp[k][x1][y1][x2][y2] = res;
}
int main(void)
{
double n, x_; // 平均值
cin >> n;
for (int i = 1; i <= 8; i++) {
for (int j = 1; j <= 8; j++) {
cin >> arr[i][j];
arr[i][j] += arr[i - 1][j] + arr[i][j - 1] - arr[i - 1][j - 1];
}
}
// 平均值
x_ = arr[8][8] / n;
memset(dp, -1, sizeof dp);
// 各个矩形的平方和
double res = dfs(n - 1, 1, 1, 8, 8);
//cout << res << " " << x_ << endl;
printf("%.3f\n", sqrt(res / n - x_ * x_));
return 0;
}