棋盘分割
题目链接:ybt高效进阶5-2-4 / luogu P5752
题目大意
给你一个矩阵,要你选一个部分进行分割,使剩下部分都是矩形,然后再选一个剩下的矩形继续分割。
分割 n-1 次得到 n 个矩形,每个矩形的权值是它们内每个点的权值的和。
要你最小化矩形权值的方差。
思路
看到方差不难想到推公式:
σ
=
∑
i
=
1
n
(
x
i
−
x
ˉ
)
2
n
\sigma = \sqrt{ \frac{ \sum_{i=1}^n (x_i - \bar x)^2 } { n }}
σ=n∑i=1n(xi−xˉ)2
我们要让这个值最小。
不难想到是要让
∑
i
=
1
n
(
x
i
−
x
ˉ
)
2
n
\frac{ \sum_{i=1}^n (x_i - \bar x)^2 } { n }
n∑i=1n(xi−xˉ)2 最小。
然后考虑化简它:
∑
i
=
1
n
(
x
i
−
x
ˉ
)
2
n
=
∑
i
=
1
n
(
x
i
2
−
2
x
i
x
ˉ
+
x
ˉ
2
)
n
=
∑
i
=
1
n
x
i
2
n
−
2
x
ˉ
2
+
x
ˉ
2
=
∑
i
=
1
n
x
i
2
n
−
x
ˉ
2
\begin{aligned}\frac{ \sum_{i=1}^n (x_i - \bar x)^2 } { n }&=\frac{ \sum_{i=1}^n (x_i^2 - 2x_i\bar x+\bar x^2) } { n }\\&=\frac{\sum_{i=1}^nx_i^2}{n}-2\bar x^2+\bar x^2\\&=\frac{\sum_{i=1}^nx_i^2}{n}-\bar x^2\end{aligned}
n∑i=1n(xi−xˉ)2=n∑i=1n(xi2−2xixˉ+xˉ2)=n∑i=1nxi2−2xˉ2+xˉ2=n∑i=1nxi2−xˉ2
由于 x ˉ = ∑ i = 1 n x i n \bar x=\frac{\sum_{i=1}^nx_i}{n} xˉ=n∑i=1nxi,故它是定值,所以我们就是要让 ∑ i = 1 n x i 2 \sum_{i=1}^nx_i^2 ∑i=1nxi2 最小。
那我们就考虑怎么搞,看到区间的分割,而且数据范围极其小,考虑直接区间 DP。
设
f
x
1
,
x
2
,
y
1
,
y
2
,
k
f_{x_1,x_2,y_1,y_2,k}
fx1,x2,y1,y2,k 为
[
x
1
,
y
1
]
[x_1,y_1]
[x1,y1] 到
[
x
2
,
y
2
]
[x_2,y_2]
[x2,y2] 的矩阵,切割
k
k
k 此能有的最小值。
然后就不难想到是枚举
x
x
x 轴分割和
y
y
y 轴分割,同时要注意的是对于每种分割,要继续分割的可能不用,要分两种情况取最小值。
其实是先枚举 k k k,然后两个维度先枚举长度再枚举区间,但我懒得搞,就直接上记搜了。
代码
#include<cmath>
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
int n;
ll xx, rem[9][9][9][9][16];
ll a[9][9];
ll get_sum(int x1, int x2, int y1, int y2) {//二维前缀和快速求区间和
return a[x2][y2] - a[x2][y1 - 1] - a[x1 - 1][y2] + a[x1 - 1][y1 - 1];
}
ll get_sum2(int x1, int x2, int y1, int y2) {
return get_sum(x1, x2, y1, y2) * get_sum(x1, x2, y1, y2);
}
ll work(int x1, int x2, int y1, int y2, int block) {
if (rem[x1][x2][y1][y2][block]) return rem[x1][x2][y1][y2][block];
//记搜
if (block == 1) return get_sum2(x1, x2, y1, y2);
rem[x1][x2][y1][y2][block] = get_sum2(x1, x2, y1, y2);
for (int i = x1; i < x2; i++) {//沿 x 轴分割
rem[x1][x2][y1][y2][block] = min(rem[x1][x2][y1][y2][block], work(x1, i, y1, y2, block - 1) + get_sum2(i + 1, x2, y1, y2));
rem[x1][x2][y1][y2][block] = min(rem[x1][x2][y1][y2][block], get_sum2(x1, i, y1, y2) + work(i + 1, x2, y1, y2, block - 1));
}
for (int i = y1; i < y2; i++) {//沿 y 轴分割
rem[x1][x2][y1][y2][block] = min(rem[x1][x2][y1][y2][block], work(x1, x2, y1, i, block - 1) + get_sum2(x1, x2, i + 1, y2));
rem[x1][x2][y1][y2][block] = min(rem[x1][x2][y1][y2][block], get_sum2(x1, x2, y1, i) + work(x1, x2, i + 1, y2, block - 1));
}
return rem[x1][x2][y1][y2][block];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= 8; i++)
for (int j = 1; j <= 8; j++) {
scanf("%lld", &a[i][j]);
a[i][j] += a[i - 1][j];
}
for (int i = 1; i <= 8; i++)
for (int j = 1; j <= 8; j++)
a[i][j] += a[i][j - 1];
xx = work(1, 8, 1, 8, n);
printf("%.3lf", sqrt(1.0 * xx / n - (1.0 * a[8][8] / n) * (1.0 * a[8][8] / n)));
//记得搞上你退出的公式
return 0;
}