$\LARGE\color{orange}{刷题日记(算法提高课)}$
$f[x_1][y_1][x_2][y_2][k]$ 表示对区域 $(x_1,y_1),\ (x_2,y_2)$ 切 $k$ 刀的所有不同切法,属性为所有切法当中均方差(标准差)的最大值
由于每一刀都可以横切也可以纵切,因此我们根据最后一刀是横还是纵进行分类
- 最后一刀是横着切,$x_1$ 到 $x_2$ 之间可以切 $x_2-x_1$ 刀,也就是有 $x_2-x_1$ 种可能,我们挨个取最小值即可
对于每一个确切的情况,我们的选择可以有两种,取上面或取下面;如果选择取上面的话,我们需要额外加上下面的方差,对于取下面的情况同理
保留上半部分,此时区间为:$(x1,y1),\ (i,y2)$
保留下半部分,此时区间为:$(i+1,y1),\ (x2,y2)$
- 最后一刀是纵着切,同样有 $y_2-y_1$ 种可能,分析思路跟第一种同理
保留左半部分,此时区间为:$(x1,y1),\ (x2,i)$
保留右半部分,此时区间为:$(x1,i+1),\ (x2,y2)$
这道题的区间很难写成循环,因此我们之间考虑记忆化搜索
由于本题的数据类型是 double
,因此在 f
数组的初始化部分,我们直接给任意一个负数即可
完整代码:
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 9, M = 16;
const double INF = 1e9;
double f[N][N][N][N][M], X;
int n, m = 8, s[N][N];
double get(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 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) return v = get(x1, y1, x2, y2);
v = INF;
for(int i = x1; i < x2; i++)//横着切
{
v = min(v, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2));//保留上半部分,将该部分的方差加起来
v = min(v, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2));//保留下半部分
}
for(int i = y1; i < y2; i++)
{
v = min(v, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2));
v = min(v, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i));
}
return v;
}
int main()
{
cin >> n;
for(int i = 1; i <= m; i++)
for(int j = 1; j <= m; j++)
{
cin >> 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, 0x8f, sizeof f);//一个负数即可
printf("%.3lf\n", sqrt(dp(1, 1, m, m, n)));
return 0;
}