学习博客: 【算法学习笔记】浅谈悬线法
- 对于一般的最大子矩阵题目,框架大概是这样的:
const int N = 100010;
int n, a[N], l[N], r[N];
ll ans;
int main() {
while (scanf("%d", &n) != EOF && n) {
ans = 0;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), l[i] = r[i] = i; // l,r边界初始化一下
for (int i = 1; i <= n; i++)
while (l[i] > 1 && a[i] <= a[l[i] - 1]) l[i] = l[l[i] - 1]; // ***
for (int i = n; i >= 1; i--)
while (r[i] < n && a[i] <= a[r[i] + 1]) r[i] = r[r[i] + 1];
for (int i = 1; i <= n; i++)
ans = max(ans, (long long)(r[i] - l[i] + 1) * a[i]);
printf("%lld\n", ans);
}
return 0;
}
- / / ∗ ∗ ∗ //~**~* // ∗∗ ∗ 对于普通的题目(博客后三题),悬线 i i i 的临时左边界是 l [ i ] l[i] l[i] ,但是当前悬线还有可能可以往左扩展,并且 l [ i ] − 1 l[i]−1 l[i]−1 位置的悬线能向左扩展到的位置, i i i 位置的悬线一定也可以扩展到,于是我们以 l [ i ] − 1 l[i]-1 l[i]−1 为跳板,将 l [ i ] l[i] l[i] 更新为 l [ l [ i ] − 1 ] l[l[i]-1] l[l[i]−1]。
-
但是上述只是普通的题目,普通在只需根据 h [ i ] h[i] h[i] 和 h [ l [ i ] − 1 ] h[l[i]-1] h[l[i]−1] 的高度大小即可判断是否可以继续向左拓展。(高度比较体现在 h [ i ] < = h [ l [ i ] − 1 ] h[i] <= h[l[i] - 1] h[i]<=h[l[i]−1] ,拓展体现在 l [ i ] = l [ l [ i ] − 1 ] l[i] = l[l[i] - 1] l[i]=l[l[i]−1] )
-
但是对于一些题目,不仅要看高度,还要一些其他的限制,例如 棋盘制作,拓展条件不仅只有高度,还有拓展边界是否可拓展(体现在 h [ l [ j ] ] ! = h [ l [ j ] − 1 ] h[l[j]] ~!= h[l[j] - 1] h[l[j]] !=h[l[j]−1])
题目:
二维:P4147 玉蟾宫
其实都差不多,只是二维需要自己算一下高度。
P1169 [ZJOI2007]棋盘制作
AC代码(特意压了一下维,只有0.6MB hahaha):
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e3+10;
int a[N], h[N], l[N], r[N];
int main()
{
int x;
int n, m; scanf("%d %d", &n, &m);
LL ans1 = 0, ans2 = 0;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++){
scanf("%d", &x);
if(i == 1) a[j] = x, h[j] = 1;
else{
if(a[j] ^ x) h[j] ++;
else h[j] = 1;
a[j] = x;
}
l[j] = r[j] = j;
}
for(int j=1; j<=m; j++)
while(l[j] > 1 && (a[l[j]] ^ a[l[j] - 1]) && h[j] <= h[l[j] - 1]) l[j] = l[l[j] - 1];
// 注意拓展条件限制更多
for(int j=m; j>=1; j--)
while(r[j] < m && (a[r[j]] ^ a[r[j] + 1]) && h[j] <= h[r[j] + 1]) r[j] = r[r[j] + 1];
for(int j=1; j<=m; j++){
LL len1 = r[j] - l[j] + 1, len2 = h[j];
ans1 = max(ans1, min(len1, len2) * min(len1, len2));
ans2 = max(ans2, len1 * len2);
}
}
printf("%lld\n%lld", ans1, ans2);
system("pause");
return 0;
}
AC代码2:
- DP思想,先预处理第 i i i 单行拓展边界,再通过 j j j 列悬线在 1 − > i − 1 1->i-1 1−>i−1 行拓展边界更新限制。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e3+10;
int a[N][N], h[N][N], l[N][N], r[N][N];
int main()
{
int x;
int n, m; scanf("%d %d", &n, &m);
LL ans1 = 0, ans2 = 0;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++) scanf("%d", &a[i][j]), h[i][j] = 1, l[i][j] = r[i][j] = j;
for(int j=2; j<=m; j++)
if(a[i][j] ^ a[i][j - 1]) l[i][j] = l[i][j - 1];
for(int j=m - 1; j>=1; j--)
if(a[i][j] ^ a[i][j + 1]) r[i][j] = r[i][j + 1];
if(i >= 2)
for(int j=1; j<=m; j++)
if(a[i][j] ^ a[i - 1][j])
h[i][j] = h[i - 1][j] + 1,
l[i][j] = max(l[i][j], l[i - 1][j]),
r[i][j] = min(r[i][j], r[i - 1][j]);
for(int j=1; j<=m; j++){
int len1 = r[i][j] - l[i][j] + 1, len2 = h[i][j];
if(len1 > len2) swap(len1, len2);
ans1 = max(ans1, 1ll * len1 * len1);
ans2 = max(ans2, 1ll * len1 * len2);
// cout << i << " " << j << " " << len1 << " " << len2 << endl;
}
// for(int j=1; j<=m; j++) cout << h[i][j] << " "; cout << endl;
}
printf("%lld\n%lld", ans1, ans2);
system("pause");
return 0;
}