AcWing 321. 棋盘分割 (二维区间dp + 记忆化搜索)

这是一个典型的二维区间dp.

我们用 f(k, x_1, y_1, x_2, y_2) 表示当前的切割的刀数为k时, 产生了一块左上角下标为(x_1, y_1)右下角下标为(x_2, y_2)的棋盘,其方差的最小值.

我们再考虑状态转移,

注意:考虑状态转移时,可以考虑这个状态是由什么状态转移而来,也可以考虑这个状态可以转移到什么状态.

在这道题,显然后者比较简单:f(k,x_1, y_1, x_2, y_2) = min(f(k + 1, {x_1}', {y_1}', {x_2}', {y_2}') + \Delta ^{2}({x_1}'', {y_1}'', {x_2}'', {y_2}''), f(k + 1, {x_1}'', {y_1}'', {x_2}'', {y_2}'') + \Delta ^{2}({x_1}', {y_1}', {x_2}', {y_2}')).

其中\Delta =\sum(x_i - \bar{x}),因为直接运算方差比较麻烦,有根号还有分数.所以我们用n\sigma ^2=\sum(x_i-\bar{x})^2代替,由于单调性与\sigma一致,所以不影响结果.

代码如下:

#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框架的总结:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值