[DP] [悬线法] 玉蟾宫 题解

题目描述

有一天,小猫rainbow和freda来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。 这片土地被分成N*M个格子,每个格子里写着’R’或者’F’,R代表这块土地被赐予了rainbow,F代表这块土地被赐予了freda。 现在freda要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着’F’并且面积最大。 但是rainbow和freda的OI水平都弱爆了,找不出这块土地,而蓝兔也想看freda卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为S,它们每人给你S两银子。

输入格式

第一行两个整数N,M,表示矩形土地有N行M列。 接下来N行,每行M个用空格隔开的字符’F’或’R’,描述了矩形土地。

输出格式

输出一个整数,表示你能得到多少银子,即(3*最大’F’矩形土地面积)的值。

样例

样例输入

5 6
R F F F F F
F F F F F F
R R R F F F
F F F F F F
F F F F F F

样例输出

45

写在前面

本题解思路受启发于浅谈用极大化思想解决最大子矩形问题中的“悬线”算法(算法2);

题解

本题实质是要我们找出一个矩阵中由“F”组成的最大子矩阵面积;

根据浅谈用极大化思想解决最大子矩形问题中的“悬线”思路,我们可以用DP来解决此问题;

定义f[i][j]表示以点(i, j)为右下角所能构成的最大矩阵,如果直接维护面积的话,那和暴力没啥区别,所以不能直接维护面积;

考虑面积的来源,一个矩阵的面积 == 长 * 高,所以只需维护长和高,最后乘起来即可;

对于长,我们可以分别维护此矩阵的左右端点,这样更方便;

怎样维护呢?考虑点(i, j);

首先,此点不能是‘R’;

然后,考虑状态转移,发现点(i, j)的左端点只和点(i - 1, j)的左端点和点(i, j)在第i行前面最后一个是‘R’的点有关;

依据短板效应,我们需要在这两个数中找最大的更新点(i, j)的左端点;

对于右端点,同理,但需要找最小的更新;

对于高,很容易想到第i层的高是由第i - 1层的高 + 这一层的高(1)转移而来;

对于这么多要维护的变量,我们很容易想到维护线段树的做法,所以按照线段树的存储方式,我们开一个二维结构体存储上述变量;

所以

总的状态转移方程

\[f[i][j].h = f[i - 1][j].h + 1 \]

\[f[i][j].r = min(f[i - 1][j].r, f[i][j].rr - 1) \]

\[f[i][j].l = max(f[i - 1][j].l, f[i][j].ll + 1) \]

其中,h为高,l为左端点,r为右端点,ll为点(i, j)在第i行前面最后一个是‘R’的点的纵坐标,rr为在第i行后面第一个是‘R’的点的纵坐标;

ll, rr, l, r需要预处理,具体见代码;

代码

#include <iostream>
using namespace std;
int n, m;
struct sss{
	long long l, r, h, ll, rr; //左右端点,高度,左边最后一个'R'位置,右边第一个'R'位置;
}f[1005][1005];
char a[1005][1005];
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
		}
	}
	for (int i = 1; i <= n; i++) { //处理左端点;
		int t = 0; //后面状态转移方程要+1,所以要赋值为0,这样+1后就可以得到正确的左端点1(对于t还等于0来说);
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'F') f[i][j].ll = t;
			else {
				f[i][j].l = -0x3f3f3f3f; //后面要找max,所以初始化为非法的-0x3f3f3f3f防止被选;
				t = j; //记录'R'出现的位置;
			}
		}
	}
	for (int i = 1; i <= n; i++) { //处理右端点;
		int t = m + 1; //后面状态转移方程要-1,所以要赋值为m + 1,这样-1后就可以得到正确的右端点m(对于t还等于m + 1来说);
		for (int j = m; j >= 1; j--) {
			if (a[i][j] == 'F') f[i][j].rr = t;
			else {
				f[i][j].r = 0x3f3f3f3f; //后面要找min,所以初始化为非法的0x3f3f3f3f防止被选;
				t = j; //记录'R'出现的位置;
			}
		}
	}
	long long ans = -1;
	for (int i = 1; i <= m; i++) f[0][i].r = 0x3f3f3f3f; //后面要找min,所以初始化为非法的0x3f3f3f3f防止被选;
	for (int i = 1; i <= m; i++) f[0][i].l = -0x3f3f3f3f; //后面要找max,所以初始化为非法的-0x3f3f3f3f防止被选;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (a[i][j] == 'F') { //注意此判断,只有当a[i][j]合法时,包含点(i, j)的矩阵才有可能是一个合法矩阵;
			f[i][j].h = f[i - 1][j].h + 1;
			f[i][j].l = max(f[i - 1][j].l, f[i][j].ll + 1);
			f[i][j].r = min(f[i - 1][j].r, f[i][j].rr - 1);
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			ans = max(ans, (f[i][j].r - f[i][j].l + 1) * f[i][j].h);
		}
	}
	cout << ans * 3;
	return 0;
}
posted @ 2024-02-22 18:13  Peppa_Even_Pig  阅读(11)  评论(0编辑  收藏  举报