【刷题】动态规划——区间DP:棋盘分割(NOI1999)【二维区间DP】

在这里插入图片描述
题目链接

每次只能沿格子水平或竖直切一刀,如样例就是竖切了1刀,横切2刀,最后竖切1刀。

先对均方差公示进行变形,将原式平方
(由于 x ˉ \bar{x} xˉ 是个定值,因此这题不变形,直接求均方差最小也行)
在这里插入图片描述

其中 x ˉ \bar{x} xˉ 是个定值,因此只要考虑 1 n ∑ i = 1 n x i 2 \frac{1}{n}\sum_{i=1}^{n}x_i^2 n1i=1nxi2

这题不再是区间,而是二维棋盘,因此状态要记棋盘左上角(x1, y1)和右下角(x2, y2)。并且限制的划分次数,状态还要记录分割数k。

f[x1, y1, x2, y2, k]表示将矩阵(x1, y1), (x2, y2),切分成k块的方案 得到的 1 n ∑ i = 1 n x i 2 \frac{1}{n}\sum_{i=1}^{n}x_i^2 n1i=1nxi2 的最小值

递推主要分两类:横切和纵切,具体可再细分
1、选择横切,并且保留上半部分。上半部分最小值f[x1, y1, i, y2, k-1],下半部分可用二维前缀和求出sum[i+1, y1, x2, y2]。
2、选择横切,并且保留下半部分。下半部分最小值f[i+1, y1, x2, y2, k-1],上半部分可用二维前缀和求出sum[x1, y1, i, y2]。
3、选择竖切,并且保留左半部分。左半部分最小值f[x1, y1, x1, j, k-1],下半部分可用二维前缀和求出sum[x1, j+1, x2, y2]。
4、选择竖切,并且保留右半部分。右半部分最小值f[x1, j+1, x2, y2, k-1],左半部分可用二维前缀和求出sum[x1, y1, x2, j]。


常规写法

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

const int N = 15, M = 9;
const double INF = 1e9;
int n, m = 8;
double f[M][M][M][M][N], X;
int s[M][M];

double sum(int x1, int y1, int x2, int y2) {
    double ans = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
	return ans * ans / n;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= m; i ++ ) {
		for (int j = 1; j <= m; j ++ ) {
			scanf("%d", &s[i][j]);
			s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 二维前缀和 
		}
	}
	X = (double)s[m][m] / n;
	for (int k = 1; k <= n; k ++ ) {
		for (int x1 = 1; x1 <= m; x1 ++ ) {
			for (int y1 = 1; y1 <= m; y1 ++ ) {
				for (int x2 = x1; x2 <= m; x2 ++ ) {
					for (int y2 = y1; y2 <= m; y2 ++ ) {
						double &v = f[x1][y1][x2][y2][k];
						v = INF;
						if (k == 1) {
							v = sum(x1, y1, x2, y2);
							continue;
						}
						
						for (int i = x1; i < x2; i ++ ) {
							v = min(v, f[x1][y1][i][y2][k - 1] + sum(i + 1, y1, x2, y2));
							v = min(v, f[i + 1][y1][x2][y2][k - 1] + sum(x1, y1, i, y2));
						}
						
						for (int j = y1; j < y2; j ++ ) {
							v = min(v, f[x1][y1][x2][j][k - 1] + sum(x1, j + 1, x2, y2));
							v = min(v, f[x1][j + 1][x2][y2][k - 1] + sum(x1, y1, x2, j));
						}
					}
				}
			}
		}
	}
	printf("%.3lf\n", sqrt(f[1][1][m][m][n] - X * X));
	return 0;
} 

记忆化搜索写法

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

const int N = 15, M = 9;
const double INF = 1e9;
int n, m = 8;
double f[M][M][M][M][N], X;
int s[M][M];

double sum(int x1, int y1, int x2, int y2) {
    double ans = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
	return ans * ans / n;
}
double dp(int x1, int y1, int x2, int y2, int k) {
	double &v = f[x1][y1][x2][y2][k];
	if (v >= 0) return v; // 已经搜索过,不再搜索 
	if (k == 1) {
		v = sum(x1, y1, x2, y2);
		return v;
	}
	v = INF;
	for (int i = x1; i < x2; i ++ ) {
		v = min(v, dp(x1, y1, i, y2, k - 1) + sum(i + 1, y1, x2, y2));
		v = min(v, dp(i + 1, y1, x2, y2, k - 1) + sum(x1, y1, i, y2));
	}
	for (int j = y1; j < y2; j ++ ) {
		v = min(v, dp(x1, y1, x2, j, k - 1) + sum(x1, j + 1, x2, y2));
		v = min(v, dp(x1, j + 1, x2, y2, k - 1) + sum(x1, y1, x2, j));
	}
	return v;
} 

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= m; i ++ ) {
		for (int j = 1; j <= m; j ++ ) {
			scanf("%d", &s[i][j]);
			s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; // 二维前缀和 
		}
	}
	X = (double)s[m][m] / n;
	memset(f, -1, sizeof f); // double科学计数法,全为1则是nan 
	printf("%.3lf\n", sqrt(dp(1, 1, m, m, n) - X * X));
	return 0;
} 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值