题目
n*m(2<=n,m<=2e5,n*m<=4e5)的网格图,只由#和.组成,其中#表示仙人掌,.表示空地
空地可以种仙人掌,但是两棵仙人掌不能相邻(初始局面保证满足条件),
问是否存在一种在当前局面下种仙人掌的方式,
使得不存在从第一行的空地走到最后一行的空地的的路径
存在的话输出YES,并输出种最少个数的仙人掌时的任意一种合法方案,否则输出NO
实际t(t<=1e3)组样例,sum n*m<=4e5
思路来源
cls
题解
首先,如果将网格图黑白染色,不同颜色的仙人掌因为不能相邻,起不到二者联合阻断的作用
所以,实际能发挥阻断作用的,只可能是同种颜色的仙人掌
考虑如果存在一种方案,一定能将上下部分切成两部分,实际即用#将网格图拦腰截断,
即存在一条同色的#路径,能从网格图左侧到网格图右侧,将网格图拦腰截断
考虑初始局面对策略的影响,
1. 已经放仙人掌的地方,代价为0
2. 其周边相邻的空地位置不能放仙人掌,代价为INF
3. 否则该空地可以放仙人掌,代价为1
对最左侧列作多源最短路,统计最右侧列点的代价即可
注意到实际可扩展的点的代价只有0、1,所以可以01bfs
代码
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=4e5+10,INF=0x3f3f3f3f;
int t,n,m,dis[N],w[N],pre[N];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
string s[N];
int f(int x,int y){
return x*m+y;
}
bool in(int x,int y){
return x>=0 && x<n && y>=0 && y<m;
}
bool upd(int &x,int y){
if(x>y){
x=y;
return 1;
}
return 0;
}
void bfs(){
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
int u=f(i,j);
if(s[i][j]=='#'){
w[u]=0;
continue;
}
bool ban=0;
for(int k=0;k<4;++k){
int ni=i+dx[k],nj=j+dy[k];
if(!in(ni,nj))continue;
ban|=(s[ni][nj]=='#');
}
if(!ban)w[u]=1;
}
}
deque<int>q;
for(int i=0;i<n;++i){
int u=f(i,0);
if(w[u]==INF)continue;
dis[u]=w[u];
if(!w[u])q.push_front(u);
else q.push_back(u);
}
while(!q.empty()){
int u=q.front();q.pop_front();
int x=u/m,y=u%m;
for(int dx:{-1,1}){
for(int dy:{-1,1}){
int nx=x+dx,ny=y+dy,v=f(nx,ny);
if(!in(nx,ny))continue;
if(dis[v]>dis[u]+w[v]){
dis[v]=dis[u]+w[v];
pre[v]=u;
if(!w[v])q.push_front(v);
else q.push_back(v);
}
}
}
}
int ans=INF,pos=-1;
for(int i=0;i<n;++i){
int u=f(i,m-1);
if(upd(ans,dis[u])){
pos=u;
}
}
if(pos==-1){
cout<<"NO"<<endl;
return;
}
for(;~pos;pos=pre[pos]){
int x=pos/m,y=pos%m;
s[x][y]='#';
}
cout<<"YES"<<endl;
for(int i=0;i<n;++i){
cout<<s[i]<<endl;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
cin>>n>>m;
for(int i=0;i<n;++i){
cin>>s[i];
for(int j=0;j<m;++j){
int u=f(i,j);
dis[u]=w[u]=INF;
pre[u]=-1;
}
}
bfs();
}
return 0;
}