题外话
之前感觉这题是棋盘DP的一个切割版本。
睡了一觉,想到了:这不就是平面的区间DP吗?
然后百度了一下,真有二维区间DP的说法。
难度
提 高 + / 省 选 − \color{blue}提高+/省选- 提高+/省选−
题意
给一个 8 × 8 8\times8 8×8的棋盘,每个格子上有一个数字。
切割一次,在剩下的两个矩形中选择一个,继续切割一次……直到剩下 n n n 个矩形。
然后问你,这个剩下的每个矩形的平方和的最小值为多少。
数据范围
1 < n < 15 1<n<15 1<n<15
思路
- 每次只能竖切或者横切,经典的棋盘切割题。
- 设 d p [ i ] [ j ] [ a ] [ b ] [ k ] dp[i][j][a][b][k] dp[i][j][a][b][k] 表示左上角(i,j)到右下角(a,b)的矩形切割成k个矩形的最小平方和
- 考虑状态转移,我们可以横切也可以竖切。并且如果切完后整个矩形会分成 k k k个矩形,那么肯定要其中一个子矩形分成1份,另一个子矩形分成 k − 1 k-1 k−1份。
- 转移方程:
h h h表示中间的切割断横。则:(画个图算一下点坐标即可)
d p [ i ] [ j ] [ a ] [ b ] [ k ] = m i n ( d p [ i ] [ j ] [ a ] [ b ] [ k ] , m i n ( d p [ i ] [ j ] [ a ] [ h ] [ 1 ] + d p [ i ] [ h + 1 ] [ a ] [ b ] [ k − 1 ] , d p [ i ] [ j ] [ a ] [ h ] [ k − 1 ] + d p [ i ] [ h + 1 ] [ a ] [ b ] [ 1 ] ) ) ; d p [ i ] [ j ] [ a ] [ b ] [ k ] = m i n ( d p [ i ] [ j ] [ a ] [ b ] [ k ] , m i n ( d p [ i ] [ j ] [ h ] [ b ] [ 1 ] + d p [ h + 1 ] [ j ] [ a ] [ b ] [ k − 1 ] , d p [ i ] [ j ] [ h ] [ b ] [ k − 1 ] + d p [ h + 1 ] [ j ] [ a ] [ b ] [ 1 ] ) ) ; dp[i][j][a][b][k] = min(dp[i][j][a][b][k],min(dp[i][j][a][h][1] + dp[i][h+1][a][b][k-1],dp[i][j][a][h][k-1] + dp[i][h+1][a][b][1]));\\ dp[i][j][a][b][k] = min(dp[i][j][a][b][k],min(dp[i][j][h][b][1] + dp[h+1][j][a][b][k-1],dp[i][j][h][b][k-1] + dp[h+1][j][a][b][1])); dp[i][j][a][b][k]=min(dp[i][j][a][b][k],min(dp[i][j][a][h][1]+dp[i][h+1][a][b][k−1],dp[i][j][a][h][k−1]+dp[i][h+1][a][b][1]));dp[i][j][a][b][k]=min(dp[i][j][a][b][k],min(dp[i][j][h][b][1]+dp[h+1][j][a][b][k−1],dp[i][j][h][b][k−1]+dp[h+1][j][a][b][1]));
初始化与一点小优化:
- 计算 d p [ i ] [ j ] [ a ] [ b ] [ 1 ] dp[i][j][a][b][1] dp[i][j][a][b][1] 就是这个子矩阵的每个元素的平方和。可以使用二位差分:
- 递推: d e [ i ] [ j ] = d e [ i − 1 ] [ j ] + d e [ i ] [ j − 1 ] − d e [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] de[i][j]=de[i-1][j]+de[i][j-1]-de[i-1][j-1]+a[i][j] de[i][j]=de[i−1][j]+de[i][j−1]−de[i−1][j−1]+a[i][j]
- 获取左上角(i,j),右下角(a,b)矩阵的和 : d e [ a ] [ b ] − d e [ i − 1 ] [ b ] − d e [ a ] [ j − 1 ] + d e [ i − 1 ] [ j − 1 ] de[a][b]-de[i-1][b]-de[a][j-1]+de[i-1][j-1] de[a][b]−de[i−1][b]−de[a][j−1]+de[i−1][j−1]
核心代码
时间复杂度 O ( 8 5 × n ) O(8^5\times n) O(85×n)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 10;
const int INF = 0x3f3f3f3f;
int de[MAX][MAX];
int dp[MAX][MAX][MAX][MAX][20];
int main()
{
int K;cin >> K;
for(int i = 1;i <= 8;++i)
for(int j = 1;j <= 8;++j){
int t;cin >> t;
de[i][j] = de[i-1][j] + de[i][j-1] - de[i-1][j-1] + t;
}
for(int i = 1;i <= 8;++i)
for(int j = 1;j <= 8;++j)
for(int a = i;a <= 8;++a)
for(int b = j;b <= 8;++b){
dp[i][j][a][b][1] = (de[a][b] - de[a][j-1] - de[i-1][b] + de[i-1][j-1]) * (de[a][b] - de[a][j-1] - de[i-1][b] + de[i-1][j-1]);
}
for(int k = 2;k <= K;++k)
for(int i = 1;i <= 8;++i)
for(int j = 1;j <= 8;++j)
for(int a = i;a <= 8;++a)
for(int b = j;b <= 8;++b){
dp[i][j][a][b][k] = INF;
for(int h = j;h < b;++h)
dp[i][j][a][b][k] = min(dp[i][j][a][b][k],min(dp[i][j][a][h][1] + dp[i][h+1][a][b][k-1],dp[i][j][a][h][k-1] + dp[i][h+1][a][b][1]));
for(int h = i;h < a;++h)
dp[i][j][a][b][k] = min(dp[i][j][a][b][k],min(dp[i][j][h][b][1] + dp[h+1][j][a][b][k-1],dp[i][j][h][b][k-1] + dp[h+1][j][a][b][1]));
}
cout << dp[1][1][8][8][K];
}