每次只能沿格子水平或竖直切一刀,如样例就是竖切了1刀,横切2刀,最后竖切1刀。
先对均方差公示进行变形,将原式平方
(由于
x
ˉ
\bar{x}
xˉ 是个定值,因此这题不变形,直接求均方差最小也行)
其中 x ˉ \bar{x} xˉ 是个定值,因此只要考虑 1 n ∑ i = 1 n x i 2 \frac{1}{n}\sum_{i=1}^{n}x_i^2 n1∑i=1nxi2。
这题不再是区间,而是二维棋盘,因此状态要记棋盘左上角(x1, y1)和右下角(x2, y2)。并且限制的划分次数,状态还要记录分割数k。
f[x1, y1, x2, y2, k]
表示将矩阵(x1, y1), (x2, y2),切分成k块的方案 得到的
1
n
∑
i
=
1
n
x
i
2
\frac{1}{n}\sum_{i=1}^{n}x_i^2
n1∑i=1nxi2 的最小值
递推主要分两类:横切和纵切,具体可再细分
1、选择横切,并且保留上半部分。上半部分最小值f[x1, y1, i, y2, k-1],下半部分可用二维前缀和求出sum[i+1, y1, x2, y2]。
2、选择横切,并且保留下半部分。下半部分最小值f[i+1, y1, x2, y2, k-1],上半部分可用二维前缀和求出sum[x1, y1, i, y2]。
3、选择竖切,并且保留左半部分。左半部分最小值f[x1, y1, x1, j, k-1],下半部分可用二维前缀和求出sum[x1, j+1, x2, y2]。
4、选择竖切,并且保留右半部分。右半部分最小值f[x1, j+1, x2, y2, k-1],左半部分可用二维前缀和求出sum[x1, y1, x2, j]。
常规写法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 15, M = 9;
const double INF = 1e9;
int n, m = 8;
double f[M][M][M][M][N], X;
int s[M][M];
double sum(int x1, int y1, int x2, int y2) {
double ans = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
return ans * ans / n;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= m; i ++ ) {
for (int j = 1; j <= m; j ++ ) {
scanf("%d", &s[i][j]);
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 二维前缀和
}
}
X = (double)s[m][m] / n;
for (int k = 1; k <= n; k ++ ) {
for (int x1 = 1; x1 <= m; x1 ++ ) {
for (int y1 = 1; y1 <= m; y1 ++ ) {
for (int x2 = x1; x2 <= m; x2 ++ ) {
for (int y2 = y1; y2 <= m; y2 ++ ) {
double &v = f[x1][y1][x2][y2][k];
v = INF;
if (k == 1) {
v = sum(x1, y1, x2, y2);
continue;
}
for (int i = x1; i < x2; i ++ ) {
v = min(v, f[x1][y1][i][y2][k - 1] + sum(i + 1, y1, x2, y2));
v = min(v, f[i + 1][y1][x2][y2][k - 1] + sum(x1, y1, i, y2));
}
for (int j = y1; j < y2; j ++ ) {
v = min(v, f[x1][y1][x2][j][k - 1] + sum(x1, j + 1, x2, y2));
v = min(v, f[x1][j + 1][x2][y2][k - 1] + sum(x1, y1, x2, j));
}
}
}
}
}
}
printf("%.3lf\n", sqrt(f[1][1][m][m][n] - X * X));
return 0;
}
记忆化搜索写法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 15, M = 9;
const double INF = 1e9;
int n, m = 8;
double f[M][M][M][M][N], X;
int s[M][M];
double sum(int x1, int y1, int x2, int y2) {
double ans = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
return ans * ans / n;
}
double dp(int x1, int y1, int x2, int y2, int k) {
double &v = f[x1][y1][x2][y2][k];
if (v >= 0) return v; // 已经搜索过,不再搜索
if (k == 1) {
v = sum(x1, y1, x2, y2);
return v;
}
v = INF;
for (int i = x1; i < x2; i ++ ) {
v = min(v, dp(x1, y1, i, y2, k - 1) + sum(i + 1, y1, x2, y2));
v = min(v, dp(i + 1, y1, x2, y2, k - 1) + sum(x1, y1, i, y2));
}
for (int j = y1; j < y2; j ++ ) {
v = min(v, dp(x1, y1, x2, j, k - 1) + sum(x1, j + 1, x2, y2));
v = min(v, dp(x1, j + 1, x2, y2, k - 1) + sum(x1, y1, x2, j));
}
return v;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= m; i ++ ) {
for (int j = 1; j <= m; j ++ ) {
scanf("%d", &s[i][j]);
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 二维前缀和
}
}
X = (double)s[m][m] / n;
memset(f, -1, sizeof f); // double科学计数法,全为1则是nan
printf("%.3lf\n", sqrt(dp(1, 1, m, m, n) - X * X));
return 0;
}