小 A 是一名 UESTC 的学生,某天他在科研楼内无聊地望着楼下的停车场。从上方看 UESTC的停车场是一个n*m的矩阵,某些位置已经停了车辆,某些位置是空的。现在正是早晨,陆陆续续有车驶入停车场并且停在了某个位置。
小 A 开始记录这些车辆,并且在有一辆车进入停车场后,他想知道现在停车场内最大的一个不含任何一辆车的正方形的边长是多少。
现在小 A 把他的记录给了你,你的任务就是帮小 A 计算出他想要的答案。
输入
第一行包含三个整数 n,m,k,分别表示停车场的大小以及记录条数。
接下来的 n 行每行包含 m 个字符,表示初始的停车场情况。字符分’X’和’.'两种,‘X’表示有一辆车,’.'表示这是个空位。
接下来 k 行每行包含两个整数 xi,yi,表示有一辆车停在了从上往下第 xi 行,从左往右第yi 列这个位置,记录是按发生的时间顺序给出的。
输出
对于每一个记录输出一个整数占一行,表示这条记录发生后对应的答案。
样例输入:
7 8 4
…
X…X.
…
…
.X…
…
…
1 5
6 4
3 5
4 6
样例输出:
5
4
4
3
数据范围:
对于 20%的数据, 1 <= n, m, k <= 200。
对于 100%的数据,1 <= n, m, k <= 2000, 1 <= xi <= n, 1 <= yi <= m。
时限:3s 空间:128MB
真的是很好的一道思维题。
首先介绍一下,当不做修改时,如何 O ( n 2 ) O(n^2) O(n2)求出所求:定义三个数组 M a x x [ i ] [ j ] , M a x y [ i ] [ j ] , M a x a [ i ] [ j ] Maxx[i][j],Maxy[i][j],Maxa[i][j] Maxx[i][j],Maxy[i][j],Maxa[i][j]分别表示从位置 ( i , j ) (i,j) (i,j)往左最长的全部是1的长度,往上的最长的全部是1的长度,以 ( i , j ) (i,j) (i,j)为右下角的最大全1正方形边长。前两个数组的转移不用说了, M a x a [ i ] [ j ] = m i n ( M a x x [ i ] [ j ] , M a x y [ i ] [ j ] , M a x a [ i − 1 ] [ j − 1 ] ) Maxa[i][j]=min(Maxx[i][j],Maxy[i][j],Maxa[i-1][j-1]) Maxa[i][j]=min(Maxx[i][j],Maxy[i][j],Maxa[i−1][j−1])正确性显而易见,画个图就出来了
再说正解,这个解法注意到了两个显而易见的事实:
这样我们就可以顺推枚举答案了,减少了一个 n n n的复杂度,但是这样的话我们需要考虑如何写check函数来检查答案是否合法呢,好像没有很好的办法,这样复杂度岂不是倒退了回去
让我们再回过头看一下修改操作,只修改一个点我们就要枚举全图的点来检查答案,实在太浪费了。如果倒过来,先把修改全部执行,再一个一个顺序撤销,我们就会发现一个对我们非常友好的事实。答案要变化只可能是包含了当前撤销的点,于是check函数就可以做到 O ( n ) O(n) O(n)了。
check(res,x,y)表示当前撤销点为 ( x , y ) (x,y) (x,y),答案是否可以为res,那么这个正方形一定要先包含res这一行,那就相当于包含 ( x , y ) (x,y) (x,y)的一条长度为res的线段,上下可以延伸的长度加起来大于等于res,定义 U p [ i ] [ j ] , D o w n [ i ] [ j ] Up[i][j],Down[i][j] Up[i][j],Down[i][j]表示从点 ( i , j ) (i,j) (i,j)往上往下最多延伸的长度,那么这条线段可以向上向下延伸的长度就为它覆盖的点的 U p D o w n UpDown UpDown的最小值,这显然可以用单调队列求。至于 U p D o w n UpDown UpDown,都可以直接暴力维护。
#include
#include
#include
#include
using namespace std;
#define N 2005
struct Node{
int l,r,v;
};
int n,m,k,res=1,sz;
int Has[N][N],Out[N],Xi[N],Yi[N],Up[N][N],Down[N][N],Min_up[N],Min_do[N],Maxa[N][N],Maxx[N][N],Maxy[N][N];
char c[N][N];
deque<Node> que;
bool check(int re,int x,int y)
{
que.clear();
int l=max(y-re+1,1),r=min(y+re-1,m);
for(int j=l;j<=r;j++)
{
int li=j;
while(!que.empty() && que.back().v>=Up[x][j]){
li=que.back().l;
que.pop_back();
}
que.push_back((Node){li,j,Up[x][j]});
if(que.front().l==j-re+1)
{
Node now=que.front();
if(now.l==j-re+1)
{
Min_up[now.l]=now.v;
if(now.l==now.r)
que.pop_front();
else
que.front().l++;
}
}
}
que.clear();
for(int j=l;j<=r;j++)
{
int li=j;
while(!que.empty() && que.back().v>=Down[x][j]){
li=que.back().l;
que.pop_back();
}
que.push_back((Node){li,j,Down[x][j]});
if(que.front().l==j-re+1)
{
Node now=que.front();
if(now.l==j-re+1)
{
Min_do[now.l]=now.v;
if(now.l==now.r)
que.pop_front();
else
que.front().l++;
}
}
}
for(int i=l;i<=r-re+1;i++)
if(Min_up[i]+Min_do[i]-1>=re)
return 1;
return 0;
}
void Ch(int x,int y)
{
Has[x][y]=1;
for(int i=1;i<=n;i++)
Up[i][y]=(Has[i][y]?Up[i-1][y]+1:0);
for(int i=n;i>=1;i--)
Down[i][y]=(Has[i][y]?Down[i+1][y]+1:0);
}
void Init()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
Maxx[i][j]=(Has[i][j]?Maxx[i][j-1]+1:0);
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
Maxy[i][j]=(Has[i][j]?Maxy[i-1][j]+1:0);
}
int Solve()
{
Init();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(Has[i][j])
Maxa[i][j]=min(min(Maxx[i][j-1],Maxy[i-1][j]),Maxa[i-1][j-1])+1;
else
Maxa[i][j]=0;
}
int ret=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ret=max(ret,Maxa[i][j]);
return ret;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<n;i++)
scanf("%s",c[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
Has[i][j]=(c[i-1][j-1]=='.');
for(int i=1;i<=k;i++){
scanf("%d%d",&Xi[i],&Yi[i]);
Has[Xi[i]][Yi[i]]=0;
}
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
Up[i][j]=(Has[i][j]?Up[i-1][j]+1:0);
for(int j=1;j<=m;j++)
for(int i=n;i>=1;i--)
Down[i][j]=(Has[i][j]?Down[i+1][j]+1:0);
Out[k]=Solve();
res=Out[k]+1;
Ch(Xi[k],Yi[k]);
for(int i=k-1;i>=1;i--)
{
for(;res<=n+1;res++)
if(!check(res,Xi[i+1],Yi[i+1]))
break;
Out[i]=res-1;
Ch(Xi[i],Yi[i]);
}
for(int i=1;i<=k;i++)
printf("%d\n",Out[i]);
return 0;
}