前言
考试做到这道题,想了很久没有结果。之后就一直寝食难安。
一天晚上,突发奇想,分治之后二分答案,发现可行。实现之后,得到一堆 W A \rm WA WA 和几个 T L E \rm TLE TLE 。晚上睡觉,惊坐起,发现可以 t w o p o i n t e r s \rm two\;pointers twopointers 。
但那已经是两天后了。
题目
思路
我一来就观察到一个性质:答案只会变小。这个性质把我引上了一条不归路……
比如,类似决策单调性的分治?这个可以排除,因为并不可能。比如,整体二分?感觉还是很有可能的。
二分答案之后,问题就转化为了每个障碍画一个大小为 w w w 的正方形,问是否覆盖整个矩形。这个感觉有一定可做性,似乎也有可能删点、加点……
可是跳出这个套子,仔细一看:原来就是一个 分治 就可以了。每次选择较长的一维,切开,过这条线的答案可以很简单的统计。
我原本也想过分治,可是当时想的是 O ( n 2 ) \mathcal O(n^2) O(n2) 枚举上下界。其实二分后使用单调队列就可以做到 O ( n log v ) \mathcal O(n\log v) O(nlogv) 。如果直接 尺取法 就可以做到 O ( n ) \mathcal O(n) O(n) 。
记录这个分治结构,显然单点修改只会涉及一条链的更新。并且这条链的更新复杂度是 n + n 2 + n 4 + ⋯ + 1 = O ( n ) n+\frac{n}{2}+\frac{n}{4}+\cdots+1=\mathcal O(n) n+2n+4n+⋯+1=O(n) 的。我们就得到了一个 O ( k n ) \mathcal O(kn) O(kn) 的做法!
我们同时发现,这个做法其实是 在线的,并且 支持移除障碍——只要把这个点所在的行和列的 “某个方向上极长可选位置” 更新,然后暴力更新分治结构即可。
所以,这道题最大的难点就是:不要相信自己。凭什么答案的单调性必须是很重要的一点?凭什么必须要整体二分、决策单调性?也许真的只是一个简单的数据结构罢了。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
inline void getMin(int &x,const int &y){
if(y < x) x = y;
}
const int MaxN = 2005;
typedef int Maze[MaxN][MaxN];
/// q[0]:head q[1]:tail [head,tail]
void maintain(int q[],int a[],int w){
while(q[1] > q[0]){
int &lst = q[q[1]-1];
if(a[lst] >= a[q[q[1]]])
lst = q[q[1]], -- q[1];
else break; // no more
}
while(q[0] < q[1] && q[q[0]] <= w)
++ q[0]; // pop_front
}
const char FENCE = 'X';
Maze lef, rig, top, ass;
int n, m; // size of maze
char str[MaxN][MaxN];
void updateRow(int i){
rep(j,1,m)
(str[i][j] == FENCE) ? (lef[i][j] = 0)
: (lef[i][j] = lef[i][j-1]+1);
drep(j,m,1)
(str[i][j] == FENCE) ? (rig[i][j] = 0)
: (rig[i][j] = rig[i][j+1]+1);
}
void updateColumn(int j){
rep(i,1,n)
(str[i][j] == FENCE) ? (ass[i][j] = 0)
: (ass[i][j] = ass[i-1][j]+1);
drep(i,n,1)
(str[i][j] == FENCE) ? (top[i][j] = 0)
: (top[i][j] = top[i+1][j]+1);
}
int tmp_q[2][MaxN+3], tmp[2][MaxN];
int updateNode(int lx,int rx,int ly,int ry){
int sl, sr; // seleted left and right
if(rx-lx <= ry-ly){ // vertical
int mid = (ly+ry)>>1;
rep(i,lx,rx){
tmp[0][i] = min(lef[i][mid],mid-ly+1);
tmp[1][i] = min(rig[i][mid+1],ry-mid);
}
sl = lx, sr = rx;
}
else{ // horizontal
int mid = (lx+rx)>>1;
rep(i,ly,ry){
tmp[0][i] = min(ass[mid][i],mid-lx+1);
tmp[1][i] = min(top[mid+1][i],rx-mid);
}
sl = ly, sr = ry;
}
int res = 0; // result
int &top0 = tmp_q[0][0] = 2;
int &top1 = tmp_q[1][0] = 2;
tmp_q[0][1] = tmp_q[1][1] = 1;
for(int i=sl,j=i-1; i<=sr; ++i){
rep(j,0,1) maintain(tmp_q[j],tmp[j],i-1);
while(j < i || tmp[0][tmp_q[0][top0]]
+tmp[1][tmp_q[1][top1]] >= j-i+1){
if(j == sr+1) break; // no more
++ j; // [i,j) is acceptable
rep(d,0,1){
tmp_q[d][++ tmp_q[d][1]] = j;
maintain(tmp_q[d],tmp[d],i-1);
}
}
res = max(res,j-i);
}
return res;
}
const int MaxM = MaxN*MaxN>>1;
int ch[MaxM][2], val[MaxM], cntNode;
void build(int lx,int rx,int ly,int ry,int &o){
// printf("building %d %d %d %d\n",lx,rx,ly,ry);
if(lx > rx || ly > ry) return void(o = 0);
o = ++ cntNode; // new node
if(rx-lx <= ry-ly){ // vertical
int mid = (ly+ry)>>1;
build(lx,rx,ly,mid-1,ch[o][0]);
build(lx,rx,mid+1,ry,ch[o][1]);
}
else{ // horizontal
int mid = (lx+rx)>>1;
build(lx,mid-1,ly,ry,ch[o][0]);
build(mid+1,rx,ly,ry,ch[o][1]);
}
val[o] = max(val[ch[o][0]],val[ch[o][1]]);
val[o] = max(val[o],updateNode(lx,rx,ly,ry));
}
void modify(int qx,int qy,int lx,int rx,int ly,int ry,int &o){
if(lx > rx || ly > ry) return ;
if(rx-lx <= ry-ly){ // vertical
int mid = (ly+ry)>>1;
if(qy < mid) // in left part
modify(qx,qy,lx,rx,ly,mid-1,ch[o][0]);
else if(mid < qy)
modify(qx,qy,lx,rx,mid+1,ry,ch[o][1]);
}
else{ // horizontal
int mid = (lx+rx)>>1;
if(qx < mid) // in left part
modify(qx,qy,lx,mid-1,ly,ry,ch[o][0]);
else if(mid < qx)
modify(qx,qy,mid+1,rx,ly,ry,ch[o][1]);
}
val[o] = max(val[ch[o][0]],val[ch[o][1]]);
val[o] = max(val[o],updateNode(lx,rx,ly,ry));
}
int main(){
n = readint(), m = readint();
int rt, k = readint();
rep(i,1,n){
scanf("%s",str[i]+1);
updateRow(i);
}
rep(j,1,m) updateColumn(j);
build(1,n,1,m,rt); // standard
for(int x,y; k; --k){
x = readint(), y = readint();
str[x][y] = FENCE; // command
updateRow(x), updateColumn(y);
modify(x,y,1,n,1,m,rt);
printf("%d\n",val[rt]);
}
return 0;
}
后记
“那,然后呢,然后呢?” 可爱的小湘妹眨了眨水汪汪的大眼睛。
“你是说那个叫 O n e I n D a r k \sf OneInDark OneInDark 的人吗?他后来就销声匿迹了。约莫还是觉得偶然性太大了,做题就像开锁,身旁有十几种钥匙,并且每个钥匙都有五十斤,要想试一试就必须付出不少的时间与精力。这次他试了三把,下一次又是多少把呢?” 她的哥哥微笑着回答了她。

博主讲述了如何在一道复杂的编程题中,跳出常规思维,发现只需简单的分治策略和单调队列就能解决。通过实例和代码展示了如何利用两指针技巧和在线更新数据结构,最终揭示问题核心并非单调性而是合适的数据结构选择。
824

被折叠的 条评论
为什么被折叠?



