[BZOJ1057][ZJOI2007]棋盘制作 (单调栈/悬线法)

来源:互联网 发布:vbscript和vb 编辑:程序博客网 时间:2024/05/22 14:56

这道题是很久很很久以前 jrfdl 考的,今天我猛然看到了我的代码,便水个题解。

首先,棋盘是黑白相间的,所以将 (行数+列数)为奇数的格子反色,就转换成了求最大全0或全1矩阵,这就简化了问题。接下来怎么做呢?网上有2种做法:单调栈和悬线法。单调栈法比较常见,我们最可能想到这种方法,看一下可以知道细节该怎么写;悬线法我第一次见到,大开眼界,也觉得非常有道理,值得学习。

这两种方法,一个循环是 rep(i,1,m) rep(j,1,n),一个是 rep(i,1,n) rep(i,1,m),时间复杂度都是 O(nm),但悬线法常数小,比单调栈更快。

1.单调栈

递推,单调栈,思维很巧
用 ri[i][j] 记录在 (i,j) 位置它前面连同它最多有几个连续的 1 ,对于每一列用一个单调不下降栈,记录边长,每一次弹出边时,更新最大正方形面积和矩形面积 ,这样就求出了最大 1 子矩阵 。
细节不多,看代码

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int N=2010, M=2010;int n,m;int a[N][M],ri[N][M];int ans1,ans2;int top, stack[N], up[N];inline void getR(){    int i,j;    for(i=1; i<=n; ++i){        for(int j=1; j<=m; ++j){            if(a[i][j]) ri[i][j]=ri[i][j-1]+1;            else ri[i][j]=0;        }    }} inline void getans(){    int i,j,to,lin;    for(j=1; j<=m; ++j){        top=0;        for(i=1; i<=n; ++i){            to = i;            while(top>0 && stack[top]>ri[i][j]){                lin = min(stack[top], i-up[top]);                ans1 = max(ans1,lin*lin);                   // 更新答案                 ans2 = max(ans2, stack[top]*(i-up[top]));                   to = min(to, up[top]);                      // 更新当前这一元素的极大全 1 矩阵,up是这一元素最多能往上延伸多少行                  top--;            }            stack[++top] = ri[i][j]; up[top]=to;        }    }}int main(){    int i,j;    scanf("%d%d",&n,&m);    for(i=1; i<=n; ++i){        for(j=1; j<=m; ++j){            scanf("%d",&a[i][j]);        }    }    // 改变棋盘,把行数+列数为奇数或偶数的位置异或  多谢 dcx%dl 提醒     for(i=1; i<=n; ++i){        for(j=1; j<=m; ++j){            if((i+j)&1) a[i][j]^=1;         }    }    // 求一下全 1 子矩阵,求一下全 0 子矩阵     getR(); getans();    for(i=1; i<=n; ++i){        for(j=1; j<=m; ++j){            a[i][j] = !a[i][j];        }    }    getR(); getans();    printf("%d\n%d\n",ans1,ans2);    return 0;}

2.悬线法

悬线法,思维巧妙
想象出一个帘子,他有他能达到的最长距离 up,最右端 topr,最左端 topl,每次算一下最大全 0 子矩阵

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int N=2010, M=2010;int n,m;int a[N][M],ri[N][M];int ans1,ans2;int topl[M],topr[M],up[M];inline void getans(){    memset(up,0,sizeof(up));    int i,j,nowl,nowr,lin;    for(j=1; j<=m; ++j){ topl[j]=1; topr[j]=m; }    for(i=1; i<=n; ++i){    // 一行一行地刷新帘子          nowl=0; nowr=m+1;        for(j=1; j<=m; ++j){    // 从左到右更新左端点              if(a[i][j]){                up[j]=0;                topl[j]=1;                nowl = j;            }else{                up[j]++;                topl[j] = max(topl[j], nowl+1);            }        }        for(j=m; j>=1; --j){    // 从右到左更新右端点              if(a[i][j]){                topr[j]=m;                nowr=j;            }else{                topr[j] = min(topr[j], nowr-1);                // 更新答案                  lin = min(up[j], topr[j]-topl[j]+1);                ans1 = max(ans1, lin*lin);                ans2 = max(ans2, up[j]*(topr[j]-topl[j]+1));            }        }    }}int main(){    int i,j;    scanf("%d%d",&n,&m);    for(i=1; i<=n; ++i){        for(j=1; j<=m; ++j){            scanf("%d",&a[i][j]);        }    }    // 改变棋盘,把行数+列数为奇数或偶数的位置异或  多谢 dcx%dl 提醒     for(i=1; i<=n; ++i){        for(j=1; j<=m; ++j){            if((i+j)&1) a[i][j]^=1;         }    }    // 求一下全 1 子矩阵,求一下全 0 子矩阵     getans();    for(i=1; i<=n; ++i){        for(j=1; j<=m; ++j){            a[i][j] = !a[i][j];        }    }    getans();    printf("%d\n%d\n",ans1,ans2);    return 0;}

在这里可以学习悬线法。

原创粉丝点击