題目描述:
題目分析:
求的是最大值,我們的答案卻在遞減,不好做,考慮倒着做,先把所有的障礙加好,再一個一個刪除障礙,這樣答案就變成遞增的了。
記當前答案爲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即可,這樣可以省去單調隊列,改爲用兩個數組。
這個方法是我在觀摩洛谷其他人的代碼的時候看到的,代碼地址。