版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题目描述:
题目分析:
求的是最大值,我们的答案却在递减,不好做,考虑倒着做,先把所有的障碍加好,再一个一个删除障碍,这样答案就变成递增的了。
记当前答案为ans,那么当删除一个障碍之后就只需要考虑包含这个点的正方形对答案的贡献。
这里我们要用到悬线法,记录每个点向左和向右最多能延伸的长度L和R。每删除一个障碍只会影响到这一行的悬线,所以可以暴力维护。
得到悬线之后考虑怎么检测是否新增了边长为ans+1的正方形,假设当前删除障碍的位置为
,新增的正方形一定过第
列,只需要检验是否存在这样的
,满足:
这相当于是一个滑动窗口问题,可以用(两个)单调队列在O(n)的时间检验,而答案<=max(n,m),所以总复杂度就是
因为检验时可以确定正方形一边的边长,而且边长递增,所以问题得到了简化和解决。
Code:
#include<bits/stdc++.h>
#define maxn 2005
using namespace std;
int n,m,k,cur,ans[maxn],L[maxn][maxn],R[maxn][maxn],f[maxn][maxn],X[maxn],Y[maxn];
bool ban[maxn][maxn];
char c[maxn];
struct Stack{
int q[maxn],v[maxn],h,t;
void clear(){h=0,t=-1;}
void push(int i,int x){while(h<=t&&v[t]>=x) t--;q[++t]=i,v[t]=x;}
int top(int d){while(q[h]<d) h++;return v[h];}
}Sl,Sr;
bool check(int u,int j){
Sl.clear(),Sr.clear();
for(int i=u;i<=n;i++){
if(ban[i][j]) return 0;
Sl.push(i,j-L[i][j]),Sr.push(i,R[i][j]-j);
if(i>=u+cur&&Sl.top(i-cur)+Sr.top(i-cur)+1>cur) return cur++,1;
}
return 0;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){
scanf("%s",c+1);
for(int j=1;j<=m;j++) if(c[j]=='X') ban[i][j]=1;
}
for(int i=1;i<=k;i++) scanf("%d%d",&X[i],&Y[i]),ban[X[i]][Y[i]]=1;
for(int i=1;i<=n;i++) ban[i][0]=ban[i][m+1]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
if(!ban[i][j]) cur=max(cur,f[i][j]=min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]))+1);
for(int j=1;j<=m;j++) L[i][j]=ban[i][j-1]?j:L[i][j-1];
for(int j=m;j>=1;j--) R[i][j]=ban[i][j+1]?j:R[i][j+1];
}
ans[k]=cur;
for(int t=k;t>=1;t--){
ban[X[t]][Y[t]]=0;
for(int j=1,i=X[t];j<=m;j++) L[i][j]=ban[i][j-1]?j:L[i][j-1];
for(int j=m,i=X[t];j>=1;j--) R[i][j]=ban[i][j+1]?j:R[i][j+1];
int u=X[t];while(u>1&&!ban[u-1][Y[t]]) u--;
while(check(u,Y[t]));
ans[t-1]=cur;
}
for(int i=1;i<=k;i++) printf("%d\n",ans[i]);
}
另外,除了单调队列,由于新增正方形必过点 ,所以我们可以O(n)预处理 表示 (即L关于(x,y)的前缀及后缀最小值)。同理预处理 表示 关于 的前缀及后缀最小值,然后枚举p点,判断 是否大于等于ans+1即可,这样可以省去单调队列,改为用两个数组。
这个方法是我在观摩洛谷其他人的代码的时候看到的,代码地址。