题意:
给定一个 8×8
的棋盘,棋盘的 每个小方格 都有一个 权值 wx,y
每次我们可以对棋盘进行一次 横切 或 竖切,将 棋盘 分成两块矩形的 子棋盘
分割完一次后,我们可以 选择两个 子棋盘 中的一个 再继续 递归 操作
可以发现题目中给的这个图片,右边这个并不是递归下去做的:他对第一次分割的两个矩形又分别进行了分割(题目要求我们只能保留一个继续分割)
现需要把棋盘按照上述分割方案,分成 n
块(n−1
次划分操作)
求一个划分方案,使得各个子棋盘的 总分的均方差最小
思路:
本题如果用 动态规划 来分析,状态表示要至少开5
维,记录分割操作的阶段,以及当前的棋盘状态
状态表示:
- 集合:将左上角坐标为 (x1, y1),右上角坐标为 (x2, y2) 的子矩阵切分成 k 部分的所有方案
- 属性:均方差(见题面中给出的公式)的最小值
状态计算:
以第一次切分为例:
对于横切从上往下一共可有 7
种不同的位置,每个位置切分之后都会有上下部分两种选择,因此共有 14
种可能
对于纵切是类似的,从左往右一共有 7
种不同的位置,每个位置切分之后会有左右部分两种选择,因此也有 14
种可能
现假设某一类方案如下:
在 xi
处横切,且选择上面部分,所有这类方案都可分为两部分:
- 继续选择上方部分切分的均方差的分值(根据定义 m i n = f ( x 1 , y 1 , i , y 2 ) min = f(x1, y1, i, y2) min=f(x1,y1,i,y2))
- 剩余部分均方差的分值(这是个定值,可结合“二维前缀和”在O(1)的复杂度内求得)
可得上述情况①的状态转移方程: d p [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] [ k ] = m i n ( d p [ x 1 ] [ y 1 ] [ i ] [ y 2 ] [ k − 1 ] + g e t ( i + 1 , y 1 , x 2 , y 2 ) ) dp[x1][y1][x2][y2][k] = min(dp[x1][y1][i][y2][k-1] + get(i+1, y1, x2, y2)) dp[x1][y1][x2][y2][k]=min(dp[x1][y1][i][y2][k−1]+get(i+1,y1,x2,y2))
同理,我们可以进而得出以下 3
类的状态转移方程
情况②,取横切的下半部分: d p [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] [ k ] = m i n ( d p [ i + 1 ] [ y 1 ] [ x 2 ] [ y 2 ] [ k − 1 ] + g e t ( x 1 , y 1 , i , y 2 ) ) dp[x1][y1][x2][y2][k] = min(dp[i+1][y1][x2][y2][k-1] + get(x1, y1, i, y2)) dp[x1][y1][x2][y2][k]=min(dp[i+1][y1][x2][y2][k−1]+get(x1,y1,i,y2))
情况③,取纵切的左半部分: d p [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] [ k ] = m i n ( d p [ x 1 ] [ y 1 ] [ x 2 ] [ j ] [ k − 1 ] + g e t ( x 1 , j + 1 , x 2 , y 2 ) ) dp[x1][y1][x2][y2][k] = min(dp[x1][y1][x2][j][k-1] + get(x1, j+1, x2, y2)) dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][j][k−1]+get(x1,j+1,x2,y2))
情况④,取纵切的右半部分: d p [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] [ k ] = m i n ( d p [ x 1 ] [ j + 1 ] [ x 2 ] [ y 2 ] [ k − 1 ] + g e t ( x 1 , y 1 , x 2 , j ) ) dp[x1][y1][x2][y2][k] = min(dp[x1][j+1][x2][y2][k-1] + get(x1, y1, x2, j)) dp[x1][y1][x2][y2][k]=min(dp[x1][j+1][x2][y2][k−1]+get(x1,y1,x2,j))
根据题目给出的公式:(xi - x̅) ^ 2 / n(xi 为某块二维矩形棋盘的总分)
显然,get
函数的编写需要结合子矩阵求前缀和的模板:
构建子矩阵,均从原点开始,
s
[
x
1
]
[
y
1
]
s[x1][y1]
s[x1][y1]表示:从原点(0, 0)
到点(x1, y1)
矩阵和
初始化:
s [ x 1 ] [ y 1 ] = s [ x 1 − 1 ] [ y 1 ] + s [ x 1 ] [ y 1 − 1 ] − s [ x 1 − 1 ] [ y 1 − 1 ] + a [ i ] [ j ] s[x1][y1] = s[x1-1][y1] + s[x1][y1-1] - s[x1-1][y1-1] + a[i][j] s[x1][y1]=s[x1−1][y1]+s[x1][y1−1]−s[x1−1][y1−1]+a[i][j]
所以子矩阵点(x1, y1)
到(x2, y2)
的和为:
s
[
x
2
]
[
y
2
]
−
s
[
x
2
]
[
y
1
−
1
]
−
s
[
x
1
−
1
]
[
y
2
]
+
s
[
x
1
−
1
]
[
y
1
−
1
]
s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]
s[x2][y2]−s[x2][y1−1]−s[x1−1][y2]+s[x1−1][y1−1]
get函数(结合二维前缀和公式)
int get_sum(int x1, int y1, int x2, int y2)
{
return s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];
}
double get(int x1, int y1, int x2, int y2)
{
double sum = get_sum(x1, y1, x2, y2) - X;
return (double)sum * sum / n;
}
时间复杂度:
O ( n 5 ) O(n^5) O(n5)
代码:
#pragma GCC optimize(2)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N = 15, M = 9;
const db inf = 1e9;
int n;
int s[M][M];//二维前缀和
db f[M][M][M][M][N];
db X;//表示平均分
int get_sum(int x1, int y1, int x2, int y2)
{
return s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];
}
db get(int x1, int y1, int x2, int y2)//含义见题解
{
db sum = get_sum(x1, y1, x2, y2) - X;
return (db)sum * sum / n;
}
db dfs(int x1, int y1, int x2, int y2, int k)
{
db &v = f[x1][y1][x2][y2][k];
if(v>=0) return v;//说明v被计算过,直接返回
if(k==1) return v = get(x1, y1, x2, y2);//k=1说明不能再切分了,即只有一部分直接返回
//如果上述都不符合则开始计算所有类别的最大值
v = inf;
for (int i=x1; i<x2; ++i)//先枚举横切(切后可以选上面部分也可以下面部分)
{
v = min(v, dfs(i+1, y1, x2, y2, k-1) + get(x1, y1, i, y2));//选上部,切完一刀后拿出一部分,因此剩余需要切分的部分就可以减一了(k-1)
v = min(v, dfs(x1, y1, i, y2, k-1) + get(i+1, y1, x2, y2));//选下部
}
for (int j=y1; j<y2; ++j)//后枚举纵切(切后可以选左边部分也可以右边部分)
{
v = min(v, dfs(x1, y1, x2, j, k-1) + get(x1, j+1, x2, y2));//选左边
v = min(v, dfs(x1, j+1, x2, y2, k-1) + get(x1, y1, x2, j));//选右边
}
return v;
}
int main()
{
cin>>n;
for (int i=1; i<=8; ++i)
for (int j=1; j<=8; ++j)
{
cin>>s[i][j];
s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1];
}
X = (db)s[8][8] / n;// 总和/n,n是最终分得的份数
memset(f, -1, sizeof f);
printf("%.3lf\n", sqrt(dfs(1, 1, 8, 8, n)));//为了方便,我们在最后才加上根号
return 0;
}