1. 程式人生 > 其它 >poj1191 棋盤分割(記憶化搜尋)

poj1191 棋盤分割(記憶化搜尋)

技術標籤:# 專題十二 基礎DP# 專題一 簡單搜尋

題目傳送門:http://poj.org/problem?id=1191

Description

將一個8*8的棋盤進行如下分割:將原棋盤割下一塊矩形棋盤並使剩下部分也是矩形,再將剩下的部分繼續如此分割,這樣割了(n-1)次後,連同最後剩下的矩形棋盤共有n塊矩形棋盤。(每次切割都只能沿著棋盤格子的邊進行)
在這裡插入圖片描述

原棋盤上每一格有一個分值,一塊矩形棋盤的總分為其所含各格分值之和。現在需要把棋盤按上述規則分割成n塊矩形棋盤,並使各矩形棋盤總分的均方差最小。
均方差 σ = Σ i = 1 n ( x i − x ˉ ) 2 n σ=\sqrt \frac{Σ_{i=1}^n(x_i-\bar x)^2}{n}

σ=nΣi=1n(xixˉ)2 ,其中平均值 x ˉ = Σ i = 1 n x i n \bar x=\frac{Σ_{i=1}^nx_i}{n} xˉ=nΣi=1nxi x i x_i xi為第i塊矩形棋盤的總分。
請程式設計對給出的棋盤及n,求出 σ σ σ 的最小值。

Input

第1行為一個整數n(1 < n < 15)。
第2行至第9行每行為8個小於100的非負整數,表示棋盤上相應格子的分值。每行相鄰兩數之間用一個空格分隔。

Output

僅一個數,為 σ σ σ(四捨五入精確到小數點後三位)。

Sample Input

3
1 1 1 1 1 1 1 3

1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3

Sample Output

1.633

Source

Noi 99

1、先分析協方差的大小與什麼有關

將協方差公式展開:

σ = Σ i = 1 n ( x i − x ˉ ) 2 n = Σ i = 1 n x i 2 − 2 Σ i = 1 n x i x ˉ + n x ˉ 2 n σ=\sqrt \frac{Σ_{i=1}^n(x_i-\bar x)^2}{n}=\sqrt \frac{Σ_{i=1}^nx_i^2-2Σ_{i=1}^nx_i\bar x+n\bar x^2}{n}

σ=nΣi=1n(xixˉ)2 =nΣi=1nxi22Σi=1nxixˉ+nxˉ2

因為均值公式為:

x ˉ = Σ i = 1 n x i n \bar x=\frac{Σ_{i=1}^nx_i}{n} xˉ=nΣi=1nxi

將均值公式代入協方差公式,有:

σ = Σ i = 1 n x i 2 n − 2 x ˉ 2 + x ˉ 2 = Σ i = 1 n x i 2 n − x ˉ 2 σ=\sqrt{\frac{Σ_{i=1}^nx_i^2}{n} -2\bar x^2+\bar x^2}=\sqrt{\frac{Σ_{i=1}^nx_i^2}{n} -\bar x^2} σ=nΣi=1nxi22xˉ2+xˉ2 =nΣi=1nxi2xˉ2

因為均值 x ˉ \bar x xˉ 已經確定,所以協方差只和 Σ i = 1 n x i 2 Σ_{i=1}^nx_i^2 Σi=1nxi2 有關

2、關於記憶化搜尋的狀態表示

用5個變量表示狀態:可以用左上角和右下角兩個格子的座標來表示整個區域,形如:(x1, y1, x2, y2),還需要使用一個變數 k 來表示需要切 k 刀

dfs(k, x1, y1, x2, y2):表示對當前區域切 k 刀後得到的最小平方和

sum(x1, y1, x2, y2):表示當前區域分數的平方

因為題目要求對當前區域進行切割後,要在切出的二個區域中選一個繼續切,另一個區域捨棄

所以狀態轉移方程為(以橫切為例):

dfs(k, x1, y1, x2, y2) = min(dfs(k - 1, x1, y1, i, y2) + sum(i + 1, y1, x2, y2), dfs(k - 1, i + 1, y1, x2, y2) + sum(x1, y1, i, y2))

縱切同理

參考的題解連結:https://www.cnblogs.com/handsomecui/p/5207512.html

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 10; 

double arr[N][N]; // 字首和陣列 
double dp[16][N][N][N][N];

// 當前矩形分數的平方
double sum(int x1, int y1, int x2, int y2)
{
	// 二維字首和 
	double x = arr[x2][y2] - arr[x1-1][y2] - arr[x2][y1-1] + arr[x1-1][y1-1];
	
	return x * x;
}
// k 表示還需要切幾刀 
double dfs(int k, int x1, int y1, int x2, int y2)
{
	// 當前狀態已經計算過 
	if (dp[k][x1][y1][x2][y2] >= 0)
		return dp[k][x1][y1][x2][y2];
	// 不需要再切 
	if (k == 0)
		return dp[k][x1][y1][x2][y2] = sum(x1, y1, x2, y2);
	
	double res = 0x3f3f3f3f;
	// 列舉橫切的位置
	for (int i = x1; i < x2; i++) {
		res = min(res, dfs(k - 1, x1, y1, i, y2) + sum(i + 1, y1, x2, y2));
		res = min(res, dfs(k - 1, i + 1, y1, x2, y2) + sum(x1, y1, i, y2));
	} 
	// 列舉縱切的位置
	for (int i = y1; i < y2; i++) {
		res = min(res, dfs(k - 1, x1, y1, x2, i) + sum(x1, i + 1, x2, y2));
		res = min(res, dfs(k - 1, x1, i + 1, x2, y2) + sum(x1, y1, x2, i));
	} 
	
	return dp[k][x1][y1][x2][y2] = res;
}

int main(void)
{
	double n, x_; // 平均值 
	cin >> n;
	for (int i = 1; i <= 8; i++) {
		for (int j = 1; j <= 8; j++) {
			cin >> arr[i][j];
			arr[i][j] += arr[i - 1][j] + arr[i][j - 1] - arr[i - 1][j - 1];
		}
	}
	// 平均值 
	x_ = arr[8][8] / n;
	memset(dp, -1, sizeof dp);
	// 各個矩形的平方和 
	double res = dfs(n - 1, 1, 1, 8, 8);
	//cout << res << " " << x_ << endl;
	printf("%.3f\n", sqrt(res / n - x_ * x_));
	
	return 0;
}