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;
}