27 NOI1999 棋盘分割 题解

棋盘分割

题面

将一个 8 \(\times\) 8 的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了 \((n-1)\) 次后,连同最后剩下的矩形棋盘共有 \(n\) 块矩形棋盘。 (每次切割都只能沿着棋盘格子的边进行)

原棋盘上每一格有一个分值(\(\le 100\)),一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成 \(n\) 块矩形棋盘,并使各矩形棋盘总分的均方差最小。

均方差 \(\sigma = \sqrt{ \frac{ \sum_{i=1}^n (x_i - \bar x)^2 } { n }}\) ,其中平均值 \(\bar x = \frac{\sum_{i=1}^n x_i}{n}\) , \(x_i\) 为第 \(i\) 块矩形棋盘的分值。

请编程对给出的棋盘及 \(n\) ,求出 \(\sigma\) 的最小值。

题解

dp方程不难想到,因为这题转移和价值计算都和矩形的位置有关系

所以一开始我设了 \(f(i,x,y,p,q)\) 表示第 \(i\) 次分割后剩下的矩阵左上角为 \((x,y)\) 右下角为 \((p,q)\) 的最小代价

每次转移时加上切掉的那一块的贡献

但是后来发现这样设是不行的,因为我并没有考虑剩下的那一块对答案的贡献

所以我们修改一下定义,将这道题转化成一个区间dp

\(f(i,x,y,p,q)\) 表示 \((x,y,p,q)\) 这个矩形分割 \(i\) 次,分割成 \(i+ 1\) 块的最小代价,初始 \(f(0,x,y,p,q) = v^2 + 2v \bar{x}\) ,目标状态为 \(f(n - 1, 1, 1, 8, 8)\)

转移的时候我们枚举删掉上下左右的哪一块,然后转移即可

时间复杂度为 \(O(15\times 8 ^ 5)\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 10;

int n, sum;
int a[N][N];
double avg, f[20][N][N][N][N];

int val (int x, int y, int p, int q) {
    return a[p][q] - a[x - 1][q] - a[p][y - 1] + a[x - 1][y - 1];
}

int main () {
    cin >> n;
    for (int i = 1; i <= 8; i ++) {
        for (int j = 1; j <= 8; j ++) {
            cin >> a[i][j];
            sum += a[i][j];
        }
    }
    avg = (double)sum / n;

    for (int i = 1; i <= 8; i ++) {
        for (int j = 1; j <= 8; j ++) {
            a[i][j] = a[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
        }
    }
    
    memset (f, 0x42, sizeof f);
    
    for (int x = 1; x <= 8; x ++) {
        for (int y = 1; y <= 8; y ++) {
            for (int p = x; p <= 8; p ++) {
                for (int q = y; q <= 8; q ++) {
                    double v = val (x, y, p, q);
                    f[0][x][y][p][q] = v * v - 2 * v * avg;
                }
            }
        }
    }

    for (int i = 1; i < n; i ++) {
        for (int x = 1; x <= 8; x ++) {
            for (int y = 1; y <= 8; y ++) {
                for (int p = x; p <= 8; p ++) {
                    for (int q = y; q <= 8; q ++) {
                        auto &now = f[i][x][y][p][q];
                        //上下
                        for (int _x = x; _x < p; _x ++) {
                            now = min (now, f[0][x][y][_x][q] + f[i - 1][_x + 1][y][p][q]);
                            now = min (now, f[i - 1][x][y][_x][q] + f[0][_x + 1][y][p][q]);
                        }
                        //左右
                        for (int _y = y; _y < q; _y ++) {
                            now = min (now, f[0][x][y][p][_y] + f[i - 1][x][_y + 1][p][q]);
                            now = min (now, f[i - 1][x][y][p][_y] + f[0][x][_y + 1][p][q]);
                        }
                    }
                }
            }
        }
    }
    double ans = sqrt((f[n - 1][1][1][8][8] + avg * avg * n) / n);

    printf ("%.3lf\n", ans);

    return 0;
}
posted @ 2025-10-05 18:06  michaele  阅读(5)  评论(0)    收藏  举报