【ybt高效进阶5-2-4】【luogu P5752】棋盘分割(区间 DP)

棋盘分割

题目链接: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 }} σ=ni=1n(xixˉ)2
我们要让这个值最小。

不难想到是要让 ∑ i = 1 n ( x i − x ˉ ) 2 n \frac{ \sum_{i=1}^n (x_i - \bar x)^2 } { n } ni=1n(xixˉ)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} ni=1n(xixˉ)2=ni=1n(xi22xixˉ+xˉ2)=ni=1nxi22xˉ2+xˉ2=ni=1nxi2xˉ2

由于 x ˉ = ∑ i = 1 n x i n \bar x=\frac{\sum_{i=1}^nx_i}{n} xˉ=ni=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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值