2 条题解
-
0
先把所有 B 连通块缩在一起,这一部分可以维护每一个点对应连通块的左上角实现,时间复杂度可以做到 。
首先有三种情况:两个 A 未对连通性产生影响的,两个 A 分别在两个位置连通若干个连通块的,两个 A 在一起连通若干个连通块的。
第一种情况只需将最大连通块大小 即可。
其余情况考虑暴力枚举一个 A 的位置。
对于第三种情况,第二个 A 一定在第一个 A 四连通的范围内,也可以暴力枚举。然后只需检查其四连通范围内连通块大小之和即可。
显然一三情况的部分时间复杂度为 。
考虑二情况,第一个加入的 A 最多只会合并 个连通块,而且下一个 A 选择的位置一定会在其合并的连通块边界上,否则一定不优。
考虑对于每一个连通块,维护出其边界上将一个 A 变为 B 所带来的贡献,将所有不同类,也就是取这个 A 使得会连通性发生不同改变的情况丢到
set里,set中按它能合并的连通块大小之和从大到小排序。我们不妨令: 为当前枚举的 A 能合并的连通块集合, 为其中任意一个元素。
此时考虑 对 边界上 A 变为 B 贡献的影响,如果它原本能合并的连通块包含一部分 ,就会让它原本能合并的连通块中 的部分直接变成一个。
这里给出一个观察:两个 B 构成的矩形,要求必须连通它们两个,则能带来不同连通性的 A 位置最多只有两个。
所以 中受影响的点最多只有 个,也就是 个,所以去除其影响只需维护 个即可。
进一步的,当 取 时一定会有两个矩形相对,所以维护 个即可。
但是这样可能会 MLE,注意到连通块个数不会超过 ,所以第一维开 即可。
所以直接枚举 ,再枚举其
set里的元素,将其对于 的贡献去掉再取 即可。时间复杂度为 。
这里给出一份参考实现。
#include<bits/stdc++.h> using namespace std; const int N=1e3+5; const int T=1e6+5; int n,fa[T],siz[T]; int v(int x,int y){ return (x-1)*n+y; } int find(int x){ return x==fa[x]?x:fa[x]=find(fa[x]); } bool check(int x,int y){ return x>=1&&x<=n&&y>=1&&y<=n; } void merge(int x,int y){ x=find(x),y=find(y); if(!x||!y||x==y)return; if(siz[x]<siz[y])swap(x,y); fa[y]=x,siz[x]+=siz[y]; } char s[N][N]; struct node{ int a[4],len,sum; node(){memset(a,0,sizeof(a)),len=sum=0;} bool operator <(const node &ret)const{ return sum<ret.sum; } bool operator ==(const node &ret)const{ if(len!=ret.len||sum!=ret.sum)return false; for(int i=0;i<len;++i) if(a[i]!=ret.a[i]) return false; return true; } }; node mx[T>>1][6]; int mp[T],tot; int ans=2; int fx[]={1,-1,0,0},fy[]={0,0,1,-1}; bool vis[T]; int ins(int i,int j){ int sum=0; for(int k=0;k<4;++k){ int x=i+fx[k],y=j+fy[k]; if(!check(x,y)||s[x][y]!='B')continue; int f=find(v(x,y)); if(!vis[f])vis[f]=1,sum+=siz[f]; } return sum; } void clear(int i,int j){ for(int k=0;k<4;++k){ int x=i+fx[k],y=j+fy[k]; if(!check(x,y)||s[x][y]!='B')continue; int f=find(v(x,y));vis[f]=0; } } int main(){ cin>>n; for(int i=1;i<=n;++i) scanf("%s",s[i]+1); for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) fa[v(i,j)]=v(i,j),siz[v(i,j)]=1; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(s[i][j]=='B'){ if(i!=1&&s[i-1][j]=='B')merge(v(i-1,j),v(i,j)); if(i!=n&&s[i+1][j]=='B')merge(v(i+1,j),v(i,j)); if(j!=1&&s[i][j-1]=='B')merge(v(i,j-1),v(i,j)); if(j!=n&&s[i][j+1]=='B')merge(v(i,j+1),v(i,j)); } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(s[i][j]!='B'){ node val; if(i!=1&&s[i-1][j]=='B')val.a[val.len++]=find(v(i-1,j)); if(i!=n&&s[i+1][j]=='B')val.a[val.len++]=find(v(i+1,j)); if(j!=1&&s[i][j-1]=='B')val.a[val.len++]=find(v(i,j-1)); if(j!=n&&s[i][j+1]=='B')val.a[val.len++]=find(v(i,j+1)); sort(val.a,val.a+val.len); for(int k=0;k<val.len;++k) val.sum+=siz[val.a[k]]; for(int k=0;k<4;++k){ int nx=i+fx[k],ny=j+fy[k]; if(!check(nx,ny)||s[nx][ny]!='B')continue; int nv=find(v(nx,ny)); if(!mp[nv])mp[nv]=++tot; nv=mp[nv]; for(int l=0;l<6;++l){ if(mx[nv][l]==val) break; if(mx[nv][l]<val){ for(int t=4;t>=l;--t) mx[nv][t+1]=mx[nv][t]; mx[nv][l]=val; break; } } } } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(s[i][j]=='B') ans=max(ans,siz[find(v(i,j))]+2); for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(s[i][j]!='B') for(int k=0;k<4;++k){ int x=i+fx[k],y=j+fy[k]; if(!check(x,y)||s[x][y]=='B')continue; int sum=ins(i,j),sum2=ins(x,y); ans=max(ans,sum+sum2+2); clear(i,j),clear(x,y); } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j){ if(s[i][j]!='B'){ int sum=0; for(int k=0;k<4;++k){ int nx=i+fx[k],ny=j+fy[k]; if(!check(nx,ny)||s[nx][ny]!='B')continue; int nv=find(v(nx,ny)); vis[nv]=1,sum+=siz[nv]; } for(int k=0;k<4;++k){ int nx=i+fx[k],ny=j+fy[k]; if(!check(nx,ny)||s[nx][ny]!='B')continue; int nv=find(v(nx,ny));nv=mp[nv]; for(int l=0;l<6;++l){ int sum2=mx[nv][l].sum; for(int t=0;t<mx[nv][l].len;++t) if(vis[mx[nv][l].a[t]]) sum2-=siz[mx[nv][l].a[t]]; ans=max(ans,sum+2+sum2); } } for(int k=0;k<4;++k){ int nx=i+fx[k],ny=j+fy[k]; if(!check(nx,ny)||s[nx][ny]!='B')continue; int nv=find(v(nx,ny)); vis[nv]=0; } } } cout<<min(ans,n*n); }实现中使用了并查集,也可以维护每个点左上角的 B 位置然后扫一遍做到严格 。
update:现在全 A 的 corner case 也可以过了。
- 1
信息
- ID
- 6013
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者


