这是一个典型的二维区间dp.
我们用 表示当前的切割的刀数为k时, 产生了一块左上角下标为右下角下标为的棋盘,其方差的最小值.
我们再考虑状态转移,
注意:考虑状态转移时,可以考虑这个状态是由什么状态转移而来,也可以考虑这个状态可以转移到什么状态.
在这道题,显然后者比较简单:.
其中,因为直接运算方差比较麻烦,有根号还有分数.所以我们用代替,由于单调性与一致,所以不影响结果.
代码如下:
#include <bits/stdc++.h>
// #define LOCAL
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
#define debug(a) cerr << #a << "=" << a << endl;
using namespace std;
const int N = 16;
int m = 8;
int n;
double s[9][9];
double X;
double f[N][N][N][N][N];
//f(k, x1, y1, x2, y2)表示切了k次以后左上角为(x1, y1)右下角为(x2, y2)的棋盘的方差的最小值
double calc(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];
double delt = sum - X;
return delt * delt;
}
double dp(int k, int x1, int y1, int x2, int y2){
if (f[k][x1][y1][x2][y2] >= 0)
return f[k][x1][y1][x2][y2];
if (k == n)
return f[k][x1][y1][x2][y2] = calc(x1, y1, x2, y2);
double t = 1e9;
//横着切
for (int i = x1; i < x2; ++i){
t = min(t, dp(k + 1, x1, y1, i, y2) + calc(i + 1, y1, x2, y2));
t = min(t, dp(k + 1, i + 1, y1, x2, y2) + calc(x1, y1, i, y2));
}
//竖着切
for (int i = y1; i < y2; ++i){
t = min(t, dp(k + 1, x1, y1, x2, i) + calc(x1, i + 1, x2, y2));
t = min(t, dp(k + 1, x1, i + 1, x2, y2) + calc(x1, y1, x2, i));
}
return f[k][x1][y1][x2][y2] = t;
}
signed main(){
#ifdef LOCAL
freopen("input.in", "r", stdin);
freopen("output.out", "w", stdout);
#endif
// IOS;
cin >> n;
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= m; ++j)
cin >> s[i][j];
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= m; ++j)
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
X = s[m][m] / (double)n;
memset(f, -1, sizeof f);
// debug(dp(1, 1, 1, m, m))
// cout << sqrt((dp(1, 1, 1, m, m) / (double)n)) << '\n';
printf("%.3f\n", sqrt((dp(1, 1, 1, m, m) / (double)n)));
}
接下来是我个人对区间dp框架的总结: