您的位置:首页 > 其它

[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),但悬线法常数小,比单调栈更快。

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;
}


在这里可以学习悬线法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  悬线法