题目其实有误,"均方差" 就是数学里的 "方差"。
题目要我们求的其实是标准差 ,即均方差 的算术平方根,公式为:
其中 是每一块切出来矩阵的值和,
是矩阵总值和平均分
块的平均值。
先提前二维前缀和出矩阵值和前缀和,顺便求出平均值。
这种切割、数据范围还很小的题,基本能确定是深搜。
深搜最重要的就是结束条件 ,考虑到切成的块数是固定的,可以增加一个变量为要切成的块数。
定义递归函数:
doouble dfs(int a, int b, int c, int d, int k)
// 返回从 (a, b) 开始 (b, c) 结束的矩阵分成 k 块最小的 (x_i-ave)^2 的和
当块数为 1 时直接输出矩阵的值和。
那么这样就好做了,每次递归枚举横着切 or 竖着切,再枚举切成的两块要求切成的块数。
时间复杂度 ,炸的震天响。
考虑记忆化搜索,因为有些状态是会重复递归的。
总状态数为 ,每个状态要经过递归函数的
的时间复杂度。
总时间复杂度为 ,可以通过。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 12;
double mat[N][N]; // 矩阵与前缀和数组
double ave; // 总值和前缀和
double dp[N][N][N][N][N]; // 记忆化搜索数组,和 dfs 对应
// [a][b][c][d][k] 从 (a, b) 开始 (b, c) 结束的矩阵分成 k 块最小的 (x_i-ave)^2 的和
double dfs(int a, int b, int c, int d, int k) {
// 返回从 (a, b) 开始 (b, c) 结束的矩阵分成 k 块最小的 (x_i-ave)^2 的和
if (dp[a][b][c][d][k]) {
return dp[a][b][c][d][k];
}
if (k == 1) {
double res = mat[c][d] - mat[a - 1][d]
- mat[c][b - 1] + mat[a - 1][b - 1];
// 前缀和容斥,这里可以自己画个网格图看看
return (res - ave) * (res - ave);
}
double res = 1e9;
for (int i = a; i < c; i ++) {
for (int j = 1; j < k; j ++) {
double t = dfs(a, b, i, d, j) + dfs(i + 1, b, c, d, k - j);
res = min(res, t);
}
}
for (int i = b; i < d; i ++) {
for (int j = 1; j <= k; j ++) {
double t = dfs(a, b, c, i, j) + dfs(a, i + 1, c, d, k - j);
res = min(res, t);
}
}
return dp[a][b][c][d][k] = res;
}
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
int a, b, n;
cin >> a >> b >> n;
memset(mat, 0, sizeof(mat));
for (int i = 1; i <= a; i ++) {
for (int j = 1; j <= b; j ++) {
cin >> mat[i][j];
}
}
for (int i = 1; i <= a; i ++) {
for (int j = 1; j <= b; j ++) {
mat[i][j] += mat[i - 1][j] + mat[i][j - 1] - mat[i - 1][j - 1];
}
}
ave = mat[a][b] / n;
memset(dp, 0, sizeof(dp));
double ans = sqrt(dfs(1, 1, a, b, n) / n);
cout << fixed << setprecision(2) << ans << "\n";
return 0;
}