[BZOJ1057][ZJOI2007]棋盘制作 (单调栈/悬线法)
2017-10-07 07:09
351 查看
这道题是很久很很久以前 jrfdl 考的,今天我猛然看到了我的代码,便水个题解。
首先,棋盘是黑白相间的,所以将 (行数+列数)为奇数的格子反色,就转换成了求最大全0或全1矩阵,这就简化了问题。接下来怎么做呢?网上有2种做法:单调栈和悬线法。单调栈法比较常见,我们最可能想到这种方法,看一下可以知道细节该怎么写;悬线法我第一次见到,大开眼界,也觉得非常有道理,值得学习。
这两种方法,一个循环是 rep(i,1,m) rep(j,1,n),一个是 rep(i,1,n) rep(i,1,m),时间复杂度都是 O(nm),但悬线法常数小,比单调栈更快。
用 ri[i][j] 记录在 (i,j) 位置它前面连同它最多有几个连续的 1 ,对于每一列用一个单调不下降栈,记录边长,每一次弹出边时,更新最大正方形面积和矩形面积 ,这样就求出了最大 1 子矩阵 。
细节不多,看代码
想象出一个帘子,他有他能达到的最长距离 up,最右端 topr,最左端 topl,每次算一下最大全 0 子矩阵
在这里可以学习悬线法。
首先,棋盘是黑白相间的,所以将 (行数+列数)为奇数的格子反色,就转换成了求最大全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 [M],ri [M]; int ans1,ans2; int top, stack , up ; 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 [M],ri [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){ fo 4000 r(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; }
在这里可以学习悬线法。
相关文章推荐
- BZOJ 1057 ZJOI2007 棋盘制作 单调栈
- [BZOJ1057]ZJOI2007棋盘制作|DP|单调栈
- BZOJ1057[ZJOI2007]棋盘制作 [单调栈]
- bzoj 1057: [ZJOI2007]棋盘制作 单调栈
- BZOJ 1057: [ZJOI2007]棋盘制作 单调栈裸题
- BZOJ 1057: [ZJOI2007]棋盘制作
- bzoj1057: [ZJOI2007]棋盘制作(悬线法)
- bzoj1057 [ZJOI2007]棋盘制作
- bzoj 1057: [ZJOI2007]棋盘制作 求最大全0/1矩阵(极大扩展矩阵)动态规划
- 【BZOJ】1057: [ZJOI2007]棋盘制作(单调栈)
- BZOJ 1057 [ZJOI2007]棋盘制作 最大子矩阵
- BZOJ1057: [ZJOI2007]棋盘制作
- BZOJ1057 [ZJOI2007]棋盘制作
- bzoj1057 [ZJOI2007]棋盘制作
- BZOJ1057:[ZJOI2007]棋盘制作——题解
- [BZOJ]1057: [ZJOI2007]棋盘制作
- [bzoj1057][ZJOI2007]棋盘制作
- 【LuoguP1169 bzoj1057】[ZJOI2007]棋盘制作
- 【bzoj1057】【ZJOI2007】【棋盘制作】【悬线法+dp】
- BZOJ 1057: [ZJOI2007]棋盘制作 悬线法求最大子矩阵+dp