这里介绍一种 O ( 玄学 ) O(\text{玄学}) O(玄学)(应该是 O ( n ( n − k ) log k ) O(n(n-k)\log k) O(n(n−k)logk)?)的做法
当时想到此做法时:计算器算一下 n 2 log n n^2\log n n2logn 好像过不了?写一下试一下
写完之后:试一下极端数据,跑了 2s+怕是过不了,交一发试一下(一不小心忘取消freopen
,还好挂在样例不扣分,再交一次)
过了 Pretest 之后:这都能 PP?坐等 FST。
第二天一看:Accepted
考虑枚举橡皮擦的位置,并分别求出橡皮擦在每个位置时,行和列分别会增加多少个 WL(White Line(s),下同)
这里以计算行上增加的 WL 为例:
先预处理出每一(有黑色的)行的黑色段的左端与右端
枚举橡皮擦在哪几行,将这几行的黑色段的左端与右端排序。然后从左往右枚举橡皮擦的位置,并维护指针 p l , p r pl,pr pl,pr,集合 s s s。
当橡皮擦移动到一个新位置时,右移 p l pl pl 扫过所有已经退出橡皮擦范围的(行的黑色段的)左端,并从集合 s s s 中删除这一行(如果 s s s 中没有这一行就打上标记,代表这一行不可能变成 WL);然后右移 p r pr pr 扫过所有已经进入橡皮擦范围的(行的黑色段的)右端,假如这一行没有标记,便将这一行插入集合 s s s。
最后枚举每一格取 max \max max 即可
下面代码有些乱,set<pair<int,pair<int,bool> > >e
可以直接排序维护,set<int> s
可以直接用一个数组记录 tag:
#include<bits/stdc++.h>
using namespace std;
int getint(){
int ans=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
bool a[2010][2010];
int rl[2010],rr[2010],cl[2010],cr[2010];//行(列)的黑色段的左(优、上、下端)
int rcnt[2010][2010],ccnt[2010][2010];//橡皮擦在此处时,行、列增加的 WL 数量
int main(){
int n=getint(),k=getint();
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
char c=getchar();
while(c>'Z'||c<'A')c=getchar();
a[i][j]=(c=='B');
}
}
memset(rl,0x3f,sizeof(rl));
memset(cl,0x3f,sizeof(cl));
int qaq=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(a[i][j]){
cl[j]=min(cl[j],i);
cr[j]=max(cr[j],i);
rl[i]=min(rl[i],j);
rr[i]=max(rr[i],j);
}
}
}
for(int i=0;i<n;i++){
qaq+=(cl[i]>cr[i]);//本身就是 WL
qaq+=(rl[i]>rr[i]);
}
for(int i=0;i<n-k+1;i++){
set<pair<int,pair<int,bool> > >e;//所有行的黑色段的左(右)端点
unordered_set<int>s,gg;//gg:被标记的集合
for(int j=i;j<i+k;j++){
if(rl[j]>rr[j])continue;//本身就是 WL 的行已经单独计算
e.insert(make_pair(rl[j],make_pair(j,0)));
e.insert(make_pair(rr[j],make_pair(j,1)));
//cout<<"r "<<rl[j]<<" "<<rr[j]<<endl;
}
auto itl=e.begin(),itr=e.begin();//即题解中的 pl,pr
for(int j=0;j<n-k+1;j++){
//cout<<"?? "<<itr->first<<" "<<itl->first<<endl;
while(itr!=e.end()&&(itr->second.second==0||itr->first<=j+k-1)){
//if(itr->second.second)cout<<">. "<<itr->second.first<<endl;
if(itr->second.second&&!gg.count(itr->second.first))
s.insert(itr->second.first);
++itr;//右移 pr
}
while(itl!=e.end()&&(itl->second.second==1||itl->first<j)){
//if(!itl->second.second)cout<<"<. "<<itl->second.first<<endl;
auto it=s.find(itl->second.first);
if(itl->second.second==0&&it!=s.end())
s.erase(it);
else gg.insert(itl->second.first);
++itl;//右移 pl
}
//上面两个指针的处理顺序可以交换
rcnt[i][j]=s.size();
//cout<<"rcnt "<<i<<" "<<j<<" "<<rcnt[i][j]<<endl;
}
}
for(int i=0;i<n-k+1;i++){//列同理
set<pair<int,pair<int,bool> > >e;
set<int>s,gg;
for(int j=i;j<i+k;j++){
if(cl[j]>cr[j])continue;
e.insert(make_pair(cl[j],make_pair(j,0)));
e.insert(make_pair(cr[j],make_pair(j,1)));
}
auto itl=e.begin(),itr=e.begin();
for(int j=0;j<n-k+1;j++){
//cout<<"?? "<<itr->first<<" "<<itl->first<<endl;
while(itr!=e.end()&&(itr->second.second==0||itr->first<=j+k-1)){
//if(itr->second.second)cout<<">. "<<itr->second.first<<endl;
if(itr->second.second&&!gg.count(itr->second.first))
s.insert(itr->second.first);
++itr;
}
while(itl!=e.end()&&(itl->second.second==1||itl->first<j)){
//if(!itl->second.second)cout<<"<. "<<itl->second.first<<endl;
auto it=s.find(itl->second.first);
if(itl->second.second==0&&it!=s.end())
s.erase(it);
else gg.insert(itl->second.first);
++itl;
}
ccnt[j][i]=s.size();
//cout<<"ccnt "<<j<<" "<<i<<" "<<ccnt[j][i]<<endl;
}
}
int ans=0;
for(int i=0;i<n-k+1;i++){
for(int j=0;j<n-k+1;j++){
//cout<<i<<" "<<j<<" "<<ccnt[i][j]<<" "<<rcnt[i][j]<<endl;
ans=max(ans,ccnt[i][j]+rcnt[i][j]);
}
}
cout<<ans+qaq;
return 0;
}