📅  最后修改于: 2023-12-03 15:12:12.383000             🧑  作者: Mango
给定一张 $n \times m$ 的纸板,将其对角线剪开,得到两个形状相同的多边形。现在,我们取其中一个多边形,并将其剪成若干个矩形。假设这些矩形可以任意拼接,并且不能重叠,求最终能够拼出的矩形的最大面积。
例如,对于如下的 $3 \times 4$ 的纸板:
+-----+-----+-----+-----+
| | | | |
| | | | |
+-----+-----+-----+-----+
| | | | |
| | | | |
+-----+-----+-----+-----+
| | | | |
| | | | |
+-----+-----+-----+-----+
将其对角线剪开,会得到两个三角形。如果我们选择下面的三角形,并将其剪成如下图红色矩形:
+-----+-----+-----+-----+
| | | | |
| | | () |
+-----+-----+-----+ |
| | | | () |
| | | () | |
+-----+-----+-----+ |
| | | () | |
| | | | |
+-----+-----+-----+-----+
那么最终能够拼成的矩形的最大面积为 $6$。
首先不难发现,最终能够拼成的矩形必定是由于纸板被对角线割开导致有一些对称性质。具体来说,我们可以将被剪下的那个多边形逆时针旋转 $90^\circ$,并将其与未被剪掉的多边形拼接成一个大矩形,如下所示:
+-----+-----+-----+-----+
| | | | () |
| | | () | |
+-----+-----+-----+ |
| | | () | |
| | | | |
+-----+-----+-----+-----+
| | | | |
| | | | |
+-----+-----+-----+-----+
最终能够拼成的矩形必定是由这个大矩形中的若干个矩形组成的。我们可以将这些矩形按照从上到下、从左到右的顺序编号,那么最终的方案就可以用一个长度为 $k$ 的序列 $(a_1,a_2,\cdots,a_k)$ 来表示,其中 $a_i$ 表示编号为 $i$ 的矩形的面积。显然,给定序列 $(a_1,a_2,\cdots,a_k)$,我们可以通过将 $a_1$ 和 $a_2$ 相邻拼接,并依次将后续的矩形拼接到这两个矩形之一的右边或下边上,从而还原出原来的不重叠矩形。
现在的问题就是,给定一个多边形沿着对角线剪开后得到的一个形状相同的多边形,求最终能够拼成的矩形的最大面积。我们可以假设这个多边形的左下角坐标为 $(0,0)$,以及将这个多边形沿着对角线剪开之后,新的多边形左下角坐标为 $(0,0)$,并将这个新的多边形逆时针旋转 $90^\circ$。这样一来,问题就转换成了在新的多边形中,选取一些整点矩形,使得它们的面积和最大。
这个问题可以使用二维数点优化来解决。具体来说,我们设 $f(i,j)$ 表示左下角为 $(0,0)$,右下角为 $(i,j)$ 的矩形中,选取一些整点矩形的面积和最大值。对于新的多边形中的一个点 $(x,y)$,可以用数点优化的方式计算出如下两个值:
显然,如果在 $(i,j)$ 这个位置上选择了一个矩形,并且这个矩形向左/下延伸的长度为 $k$,那么这个矩形的面积为 $(k+1) \times (k+1)$。因此,我们可以得到转移方程:
$$ f(i,j) = \max_{k=0}^{L_{i,j}}{f(i-k-1,j) + (k+1) \times (D_{i-k-1,j}+1)} \ \quad\quad,,, + \max_{k=0}^{D_{i,j}}{f(i,j-k-1) + (k+1) \times (L_{i,j-k-1}+1)} $$
其中,第一项表示沿着水平方向需要选取的矩形,第二项表示沿着竖直方向需要选取的矩形。
最终的答案为 $f(n-1,m-1)$。
def solve(n: int, m: int) -> int:
left = [[0] * m for _ in range(n)]
down = [[0] * m for _ in range(n)]
for i in range(n):
for j in range(m):
if (i + j) % 2 == 0:
left[i][j] = 1 if j == 0 else left[i][j-1] + 1
down[i][j] = 1 if i == 0 else down[i-1][j] + 1
else:
left[i][j] = 0
down[i][j] = 0
f = [[0] * m for _ in range(n)]
for i in range(n):
for j in range(m):
if (i + j) % 2 == 1:
continue
for k in range(left[i][j]):
f[i][j] = max(f[i][j], f[i-k-1][j] + (k+1)*(down[i-k-1][j]+1))
for k in range(down[i][j]):
f[i][j] = max(f[i][j], f[i][j-k-1] + (k+1)*(left[i][j-k-1]+1))
return f[n-1][m-1]
n, m = map(int, input().split())
print(solve(n, m))
其中的 left
数组和 down
数组表示向左和向下延伸的最大长度。当 $(i+j)$ 为偶数时,表示 $(i,j)$ 这个位置上的点在旋转后是在对角线上,因此需要计算它的 left
和 down
。当 $(i+j)$ 为奇数时,表示 $(i,j)$ 这个位置在旋转后不在对角线上,因此其 left
和 down
都为 $0$。
具体的实现中,使用了一个 continue
语句来忽略不合法的 $(i,j)$。如果某个位置 $(i,j)$ 不在对角线上,那么其 left
和 down
都为 $0$,此时计算 f[i][j]
的值没有意义,因此可以直接跳过。