[DP專題]懸線法


參考:https://blog.csdn.net/twtsa/article/details/8120269

先給出題目來源:(洛谷)

1.p1387 最大正方形

2.P1169 棋盤制作

3.p2701 巨大的牛棚 

4.p4147 玉蟾宮

5.P1578 奶牛浴場

 

......

懸線法,很好理解,就是懸一根線晃來晃去求最大子矩陣嘛!

思路轉移方程也很簡單:

if(滿足^&%$!@#^%){
    right[i][j]=min(right[i][j],right[i-1][j]);
    left[i][j]=max(left[i][j],left[i-1][j]);
    up[i][j]=up[i-1][j]+1;
}

 

下面解釋一下:

right表示從(i,j)這個點出發向右能到達最遠的距離

left和up差不多,一個向左,一個向上

關於初始化

for(int i=1;i<=n;i++) 
    for(int j=1;j<=m;j++)
        right[i][j]=left[i][j]=j,up[i][j]=1;
for(int i=1;i<=n;i++)
    for(int j=2;j<=m;j++)
        if(滿足條件)
            right[i][j]=right[i][j-1];
for(int i=1;i<=n;i++)
    for(int j=m-1;j>=1;j--)
        if(滿足條件)
            left[i][j]=left[i][j+1];

其實這個東西跟模板一樣套就好了





 

【NO.1】最大正方形

【解法1】

  數據這么小,考慮暴力:

  維護矩陣二維前綴和,暴力枚舉左上角和正方形的長,判斷該塊矩陣和是否為長*長,更新最大值

  復雜度:O(n^3)

  (很久以前以前寫的代碼,可能有點丑)

    #include<iostream>
    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int n,m,map[105][105];
    int sum[105][105];
    void pre(){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+map[i][j];  
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                scanf("%d",&map[i][j]);
        pre();
        int ans=-1;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                for(int l=1;l<=min(n,m);l++){
                    int rx=i+l-1,ry=j+l-1;
                    if(i-1+l>n||j-1+l>m||sum[rx][ry]-sum[rx][j-1]-sum[i-1][ry]+sum[i-1][j-1]!=l*l) break;
                    if(ans<l) ans=l;
                }
        printf("%d",ans);
        return 0;
    }

【缺點】但是如果n=5000之類稍微大一點的數據就GG了,所以接下來我們用懸線法解決這個問題

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    inline int read(){
        char chr=getchar();    int f=1,ans=0;
        while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
        while(isdigit(chr))  {ans=(ans<<3)+(ans<<1);ans+=chr-'0';chr=getchar();}
        return ans*f;
    }
    void write(int x){
        if(x<0) putchar('-'),x=-x;
        if(x>9) write(x/10);
        putchar(x%10+'0');
    }
    int n,m,a[4005][4005];
    int l[4005][4005],r[4005][4005],up[4005][4005],ans1,ans2;
    int main(){
        n=read(),m=read();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                a[i][j]=read(),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]&&a[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(a[i][j]==a[i][j+1]&&a[i][j]==1)    r[i][j]=r[i][j+1];//預處理
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                if(i>1)
                    if(a[i][j]==a[i-1][j]&&a[i][j]==1){//滿足條件
                        l[i][j]=max(l[i][j],l[i-1][j]);
                        r[i][j]=min(r[i][j],r[i-1][j]);
                        up[i][j]=up[i-1][j]+1;
                    }
                int a=r[i][j]-l[i][j]+1;
                int b=min(a,up[i][j]);
                ans1=max(b,ans1);//更新答案
            }
        cout<<ans1;
        return 0;
    }

  有沒有發現,其實就是模板里面把條件加上去就OK了,驚不驚喜!!!哈哈哈(其實好像下面大部分都是這樣的)比如下面這題

 

【NO.2】棋盤制作

一樣是套模板,改一下條件

注意到該題條件是10間隔分布,則if語句中內容應為:if(a[i][j]!=a[i-1][j])注意這里還有一個大前提就是i>1!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
    char chr=getchar();    int f=1,ans=0;
    while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
    while(isdigit(chr))  {ans=(ans<<3)+(ans<<1);ans+=chr-'0';chr=getchar();}
    return ans*f;
}
void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n,m,a[4005][4005];
int l[4005][4005],r[4005][4005],up[4005][4005],ans1,ans2;
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=read(),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];
    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];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(i>1)
                if(a[i][j]!=a[i-1][j]){
                    l[i][j]=max(l[i][j],l[i-1][j]);
                    r[i][j]=min(r[i][j],r[i-1][j]);
                    up[i][j]=up[i-1][j]+1;
                }
            int a=r[i][j]-l[i][j]+1;
            int b=min(a,up[i][j]);
            ans1=max(b*b,ans1);
            ans2=max(a*up[i][j],ans2);
        }
    cout<<ans1<<"\n"<<ans2;
    return 0;
}

 【NO.3】巨大的牛棚

還是模板?只不過讀入的時候轉換一下就好了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
    char chr=getchar();    int f=1,ans=0;
    while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
    while(isdigit(chr))  {ans=(ans<<3)+(ans<<1);ans+=chr-'0';chr=getchar();}
    return ans*f;
}
void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n,T;
int a[1005][1005],l[1005][1005],r[1005][1005],u[1005][1005],ans;
int main(){
    n=read();T=read();
    for(int i=1;i<=T;i++){int x=read(),y=read();a[x][y]=1;}
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            l[i][j]=r[i][j]=j,u[i][j]=1;
    for(int i=1;i<=n;i++)
        for(int j=2;j<=n;j++)
            if(a[i][j]==0&&a[i][j-1]==0) l[i][j]=l[i][j-1];
    for(int i=1;i<=n;i++)
        for(int j=n-1;j>=1;j--)
            if(a[i][j]==0&&a[i][j+1]==0) r[i][j]=r[i][j+1];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(i>1&&a[i][j]==0&&a[i-1][j]==0){
                u[i][j]=u[i-1][j]+1;
                l[i][j]=max(l[i-1][j],l[i][j]);
                r[i][j]=min(r[i-1][j],r[i][j]);
            }
            int a=r[i][j]-l[i][j]+1;
            int b=min(a,u[i][j]);
            ans=max(ans,b);
        }
    cout<<ans;
    return 0;
}

【NO.4】玉蟾宮

這些題幾乎一樣...都不想說什么了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
    char chr=getchar();    int f=1,ans=0;
    while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
    while(isdigit(chr))  {ans=(ans<<3)+(ans<<1);ans+=chr-'0';chr=getchar();}
    return ans*f;
}
void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n,m;
char a[4005][4005];
int l[4005][4005],r[4005][4005],up[4005][4005],ans1,ans2;
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
            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]&&a[i][j]=='F')    l[i][j]=l[i][j-1];
    for(int i=1;i<=n;i++)
        for(int j=m-1;j>=1;j--)
            if(a[i][j]==a[i][j+1]&&a[i][j]=='F')    r[i][j]=r[i][j+1];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(i>1)
                if(a[i][j]==a[i-1][j]&&a[i-1][j]=='F'){
                    l[i][j]=max(l[i][j],l[i-1][j]);
                    r[i][j]=min(r[i][j],r[i-1][j]);
                    up[i][j]=up[i-1][j]+1;
                }
            int a=r[i][j]-l[i][j]+1;
            int b=min(a,up[i][j]);
            ans2=max(a*up[i][j],ans2);
        }
    cout<<ans2*3;
    return 0;
}

當然,難的在這里:奶牛浴場 (上面看懂的同學可以挑戰一下這題)

這里有題解噢~

占坑,別禁賽噢~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM