poj1191 棋盤分割(記憶化搜尋)
題目傳送門: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,求出
σ
σ
σ 的最小值。
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 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}
因為均值公式為:
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=1nxi2−2xˉ2+xˉ2 =nΣi=1nxi2−xˉ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;
}