1. 程式人生 > >最大子矩形問題的解決方法:懸線法

最大子矩形問題的解決方法:懸線法

給出一道板子題

洛谷4147 玉蟾宮

題目背景

有一天,小貓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'矩形土地面積)的值。

輸入輸出樣例

輸入樣例#1: 複製
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
輸出樣例#1: 複製
45

說明

對於50%的資料,1<=N,M<=200

對於100%的資料,1<=N,M<=1000

//花有重開日,人有少年時!致敬砍下50分的羅斯

讓我們像羅斯一樣,永不言棄。這道題雖然有著提高加的難度,我們仍要解決它!

這種以O(NM)的時間複雜度解決它的特殊方法,叫做懸線法。

轉載一波:

定義

有效豎線:除了兩個端點外,不覆蓋任何一個障礙點的豎直線段。

懸線:上端覆蓋了一個障礙點或者到達整個矩形上邊界的有效線段。

每個懸線都與它底部的點一一對應,矩形中的每一個點(矩形頂部的點除外)都對應了一個懸線。

懸線的個數=(N-1)*M;

如果把一個極大子矩形按照橫座標的不同切割成多個與y軸平行的線段,那麼其中至少有一個懸線。

如果把一個懸線向左右兩個方向儘可能的移動,那麼就得到了一個矩形,我們稱它為懸線對應的矩形。

懸線對應的矩形不一定是極大子矩形,因為下邊界可能還可以向下擴充套件。

設計演算法:

原理:所有懸線對應矩形的集合一定包含了極大子矩形的集合。 通過列舉所有的懸線,找出所有的極大子矩形。 演算法規模:           懸線個數=(N-1)×M           極大子矩形個數≤懸線個數

 

具體方法:

設 H[i,j]為點(i,j)對應的懸線的長度。      L[i,j]為點(i,j)對應的懸線向左最多能夠移動到的位置。      R[i,j]為點(i,j)對應的懸線向右最多能夠移動到的位置。 考慮點(i,j)對應的懸線與點(i-1,j)對應的懸線的關係(遞推思想):  如果(i-1,j)為障礙點,那麼,如圖所示,(i,j)對應的懸線長度1,左右能移動到的位置是整個矩形的左右邊界。 即 H[i,j]=1,    L[i,j]=0,R[i,j]=m
如果(i-1,j)不是障礙點,那麼,如圖所示,(i,j)對應的懸線長度為(i-1,j)對應的懸線長度+1。 即 H[i,j]=H[i-1,j]+1 •如果(i-1,j)不是障礙點,那麼,如圖所示,(i,j)對應的懸線左右能移動的位置要在(i-1,j)的基礎上變化。                     L[i-1,j] L[i,j]=max                     (i-1,j)左邊第一個障礙點的位置         •同理,也可以得到R[i,j]的遞推式                         R[i-1,j]      R[i,j]=min                             (i-1,j)右邊第一個障礙點的位置          以此思想,我們得出瞭解決此題的方法。 但我的第一次提交是這樣寫的:
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
int n,m;
int map[1003][1003],xuxi[1003][1003],l[1003][1003],r[1003][1003];
char c; 
int read()
{
    char c=getchar();
    while(c!='F'&&c!='R')c=getchar();
    if(c=='F')return 1;
    else return 0;
}
int main()
{
    memset(map,0,sizeof(map));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {  
       map[i][j]=read();
       l[i][j]=r[i][j]=j;xuxi[i][j]=1;
    }
    for(int i=1;i<=n;i++)
    for(int j=2;j<=m;j++)
    if(map[i][j]&&map[i][j-1])
    l[i][j]=l[i][j-1];
    for(int i=1;i<=n;i++)
    for(int j=m-1;j>=1;j--)
    if(map[i][j]&&map[i][j+1])
    r[i][j]=r[i][j+1];
    int ans=0;
//注意這裡出錯了
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(map[i][j]&&map[i-1][j]) { xuxi[i][j]=xuxi[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]); ans=max(ans,xuxi[i][j]*(r[i][j]-l[i][j]+1));//這句話的位置不對 } printf("%d",ans*3); return 0; }

看似沒有錯誤,實則一交只得90······

原因是,構造這樣一組資料即可卡死這個程式:

5 5

R R R R R

R R R R R

R R R F R

R R R R R

R R R R R

 

本該輸出3,卻輸出了0.

 

所以,應該這樣寫,以下才是標準程式:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
int n,m;
int map[1003][1003],xuxi[1003][1003],l[1003][1003],r[1003][1003];
char c; 
int read()
{
    char c=getchar();
    while(c!='F'&&c!='R')c=getchar();
    if(c=='F')return 1;
    else return 0;
}
int main()
{
    memset(map,0,sizeof(map));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {  
       map[i][j]=read();
       l[i][j]=r[i][j]=j;xuxi[i][j]=1;
    }
    for(int i=1;i<=n;i++)
    for(int j=2;j<=m;j++)
    if(map[i][j]&&map[i][j-1])
    l[i][j]=l[i][j-1];
    for(int i=1;i<=n;i++)
    for(int j=m-1;j>=1;j--)
    if(map[i][j]&&map[i][j+1])
    r[i][j]=r[i][j+1];
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(map[i][j]&&map[i-1][j])
            {
            xuxi[i][j]=xuxi[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]);
            }
            ans=max(ans,xuxi[i][j]*(r[i][j]-l[i][j]+1));
        }
        
    printf("%d",ans*3);
    return 0;
} 

即可輕鬆AC。

總結:最大子矩形,求法精妙,細節注意。