算法学习笔记:悬线法 DP

算法学习笔记:悬线法 DP

1. 前言

悬线法 DP,是一种 DP,用来处理矩阵类问题。

这种 DP 一般处理的问题长这样:

给出一个 n × m n \times m n×m 的矩阵,问满足条件的最大子矩阵的面积是多少?

当然也可以问边长之类的。

这类问题通常有非悬线法 DP 的解法,但是悬线法 DP 往往能够减小思维量,减小出错率。

2. 详解

例题:P1387 最大正方形

这道题有两种方法:普通 DP 与 悬线法 DP。

普通 DP?

f i , j f_{i,j} fi,j 表示处理到 a i , j a_{i,j} ai,j 时的最大值,那么有转移方程:

f i , j = min ⁡ { f i − 1 , j , f i , j − 1 , f i − 1 , j − 1 ∣ a i , j = 1 } + 1 f_{i,j}=\min\{f_{i-1,j},f_{i,j-1},f_{i-1,j-1}|a_{i,j}=1\}+1 fi,j=min{fi1,j,fi,j1,fi1,j1ai,j=1}+1

转移方程应该还是好想的吧。

那么普通 DP 以优秀的表现通过了这道题。

那么悬线法 DP 呢?

悬线法 DP 的一般思路就是:处理出每一个点向左( l i , j l_{i,j} li,j),向右( r i , j r_{i,j} ri,j)能够扩展的 位置,以及向上( U p i , j Up_{i,j} Upi,j)能够扩展的 距离

什么意思呢?看下面这张图。

在这里插入图片描述

这样做有什么用处吗?

好处就是:如果我们以 ( i , j ) (i,j) (i,j) 为矩形的底边,那么这个最大子矩形实际上就已经确定了!

因为我们知道往左边能最多扩展多少,往右边最多扩展多少,往上面还能够扩展多少,如图:

在这里插入图片描述

那么首先我们要预处理一下 l , r , U p l,r,Up l,r,Up,递推式如下:

l i , j = l i , j − 1 , r i , j = r i , j + 1 , U p i , j = U p i − 1 , j + 1 l_{i,j}=l_{i,j-1},r_{i,j}=r_{i,j+1},Up_{i,j}=Up_{i-1,j}+1 li,j=li,j1,ri,j=ri,j+1,Upi,j=Upi1,j+1

转移条件:相邻两个全部都符合题意,也就是可以作为一个子矩阵。

初始值:如果 a i , j = 1 a_{i,j}=1 ai,j=1 l i , j = r i , j = j , U p i , j = 1 l_{i,j}=r_{i,j}=j,Up_{i,j}=1 li,j=ri,j=j,Upi,j=1

这部分的代码如下:

for (int i = 1; i <= n; ++i)
	for (int j = 1; j <= m; ++j)
		if (a[i][j] == 1) l[i][j] = r[i][j] = j, Up[i][j] = 1;//初始化
for (int i = 1; i <= n; ++i)
	for (int j = 2; j <= m; ++j)
		if (a[i][j] && a[i][j - 1]) l[i][j] = l[i][j - 1];//l
for (int i = 1; i <= n; ++i)
	for (int j = m - 1; j >= 1; --j)
		if (a[i][j] && a[i][j + 1]) r[i][j] = r[i][j + 1];//r
for (int i = 2; i <= n; ++i)
	for (int j = 1; j <= m; ++j)
		if (a[i][j] && a[i - 1][j]) Up[i][j] = Up[i - 1][j] + 1;//Up

然后如何确定最大子矩阵呢?

如果你的想法是直接用 l i , j , r i , j , U p i , j l_{i,j},r_{i,j},Up_{i,j} li,j,ri,j,Upi,j 推答案,那么考虑一下下面这张图:

在这里插入图片描述

在上图中,真正的矩形是橙色的矩形,但是如果你直接推答案就会变成黑色的矩形,显然答案是错的。

因此我们要对 l , r l,r l,r 做一点修改。

考虑一下就会发现, l , r l,r l,r 就是从能够到达最上面的点,到这个点中原先 l , r l,r l,r 中的最大/最小值。

那么递推式就是这样:

l i , j = max ⁡ { l i , j , l i − 1 , j ∣ a i , j = a i − 1 , j = 1 } l_{i,j}=\max\{l_{i,j},l_{i-1,j}|a_{i,j}=a_{i-1,j}=1\} li,j=max{li,j,li1,jai,j=ai1,j=1}

r i , j = min ⁡ { r i , j , r i − 1 , j ∣ a i , j = a i − 1 , j = 1 } r_{i,j}=\min\{r_{i,j},r_{i-1,j}|a_{i,j}=a_{i-1,j}=1\} ri,j=min{ri,j,ri1,jai,j=ai1,j=1}

需要注意第一行不能递推。

答案:

如果是求矩形的面积,就是 max ⁡ { ( r i , j − l i , j + 1 ) × U p i , j ∣ i ∈ [ 1 , n ] , j ∈ [ 1 , m ] } \max\{(r_{i,j}-l_{i,j}+1) \times Up_{i,j}|i \in [1,n],j \in [1,m]\} max{(ri,jli,j+1)×Upi,ji[1,n],j[1,m]}

但是因为这道题球的是正方形的边长, r i , j − l i , j + 1 r_{i,j}-l_{i,j}+1 ri,jli,j+1 U p i , j Up_{i,j} Upi,j 取最小值即可。

这一部分的代码:

for (int i = 1; i <= n; ++i)
	for(int j = 1; j <= m; ++j)
	{
		if ((i ^ 1) && a[i][j] != 0 && a[i - 1][j] != 0)//特别注意第一行不能转移
		{
			l[i][j] = Max(l[i][j], l[i - 1][j]);
			r[i][j] = Min(r[i][j], r[i - 1][j]);
		}
		ans = Max(ans, Min(Up[i][j], r[i][j] - l[i][j] + 1));
	}

总代码:

/*
========= Plozia =========
	Author:Plozia
	Problem:P1387 最大正方形
	Date:2021/3/13
========= Plozia =========
*/

#include <bits/stdc++.h>

typedef long long LL;
const int MAXN = 100 + 10;
int n, m, a[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN], Up[MAXN][MAXN], ans;

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
	return (fh == 1) ? sum : -sum;
}
int Max(int fir, int sec) {return (fir > sec) ? fir : sec;}
int Min(int fir, int sec) {return (fir < sec) ? fir : sec;}

int main()
{
	n = read(), m = read();
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= m; ++j)
			a[i][j] = read();
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= m; ++j)
			if (a[i][j] == 1) l[i][j] = r[i][j] = j, Up[i][j] = 1;//初始化
	for (int i = 1; i <= n; ++i)
		for (int j = 2; j <= m; ++j)
			if (a[i][j] && a[i][j - 1]) l[i][j] = l[i][j - 1];//l
	for (int i = 1; i <= n; ++i)
		for (int j = m - 1; j >= 1; --j)
			if (a[i][j] && a[i][j + 1]) r[i][j] = r[i][j + 1];//r
	for (int i = 2; i <= n; ++i)
		for (int j = 1; j <= m; ++j)
			if (a[i][j] && a[i - 1][j]) Up[i][j] = Up[i - 1][j] + 1;//Up
	for (int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
		{
			if ((i ^ 1) && a[i][j] != 0 && a[i - 1][j] != 0)//特别注意第一行不能转移
			{
				l[i][j] = Max(l[i][j], l[i - 1][j]);
				r[i][j] = Min(r[i][j], r[i - 1][j]);
			}
			ans = Max(ans, Min(Up[i][j], r[i][j] - l[i][j] + 1));
		}
	printf("%d\n", ans);
	return 0;
}

3. 练习题

练习题传送门:DP算法总结&专题训练4(悬线法 DP)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值