1. 程式人生 > 其它 >DP專題-學習筆記:懸線法 DP

DP專題-學習筆記:懸線法 DP

目錄

1. 前言

懸線法 DP,是一種 DP,用來處理矩陣類問題。

這種 DP 一般處理的問題長這樣:

給出一個 \(n \times m\) 的矩陣,問滿足條件的最大子矩陣的面積是多少?

當然也可以問邊長之類的。

這類問題通常有非懸線法 DP 的解法,但是懸線法 DP 往往能夠減小思維量,減小出錯率。

2. 詳解

例題:P1387 最大正方形

這道題有兩種方法:普通 DP 與 懸線法 DP。

普通 DP?

\(f_{i,j}\) 表示處理到 \(a_{i,j}\) 時的最大值,那麼有轉移方程:

\[f_{i,j}=\min\{f_{i-1,j},f_{i,j-1},f_{i-1,j-1}|a_{i,j}=1\}+1 \]

轉移方程應該還是好想的吧。

那麼普通 DP 以優秀的表現通過了這道題。

那麼懸線法 DP 呢?

懸線法 DP 的一般思路就是:處理出每一個點向左(\(l_{i,j}\)),向右(\(r_{i,j}\))能夠擴充套件的 位置,以及向上(\(Up_{i,j}\))能夠擴充套件的 距離

什麼意思呢?看下面這張圖。

這樣做有什麼用處嗎?

好處就是:如果我們以 \((i,j)\) 為矩形的底邊,那麼這個最大子矩形實際上就已經確定了!

因為我們知道往左邊能最多擴充套件多少,往右邊最多擴充套件多少,往上面還能夠擴充套件多少,如圖:

那麼首先我們要預處理一下 \(l,r,Up\),遞推式如下:

\[l_{i,j}=l_{i,j-1},r_{i,j}=r_{i,j+1},Up_{i,j}=Up_{i-1,j}+1 \]

轉移條件:相鄰兩個全部都符合題意,也就是可以作為一個子矩陣。

初始值:如果 \(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 = 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},Up_{i,j}\) 推答案,那麼考慮一下下面這張圖:

在上圖中,真正的矩形是橙色的矩形,但是如果你直接推答案就會變成黑色的矩形,顯然答案是錯的。

因此我們要對 \(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\} \] \[r_{i,j}=\min\{r_{i,j},r_{i-1,j}|a_{i,j}=a_{i-1,j}=1\} \]

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

答案:

如果是求矩形的面積,就是 \(\max\{(r_{i,j}-l_{i,j}+1) \times Up_{i,j}|i \in [1,n],j \in [1,m]\}\)

但是因為這道題球的是正方形的邊長,\(r_{i,j}-l_{i,j}+1\)\(Up_{i,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)