首先对所有询问进行离线处理,因为是网格图,所以考虑用分治来解决。
对 ([1,n]) 进行分治,对于区间 ([l,r]),在 (mid) 两侧的点对互相要到达一定会经过 (mid) 所在的直线,因此每次处理出 (f_{i,j,k}) 和 (g_{i,j,k}),(f_{i,j,k}) 为点 ((i,j)) 是否能到达点 ((k,mid)),(g_{i,j,k}) 为点 ((k,mid)) 是否能到达点 ((i,j))。
对于分别在 (mid) 两侧的点对,直接通过 (f,g) 取并即可回答询问,只在一侧的点对就继续分治下去。
将 (n,m) 看作同阶,得复杂度为 (O((n^3+q)log n)),无法接受,对 (f,g) 用 (bitset) 来优化后复杂度为 (O((frac{n^3}{w}+q)log n)),即可通过。
#include<bits/stdc++.h>
#define maxn 510
#define maxm 600010
using namespace std;
template<typename T> inline void read(T &x)
{
x=0;char c=getchar();bool flag=false;
while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
if(flag)x=-x;
}
int n,m,t;
bool ans[maxm];
char w[maxn][maxn];
bitset<maxn> f[maxn][maxn],g[maxn][maxn];
struct query
{
int x1,y1,x2,y2,id;
}q[maxm],q1[maxm],q2[maxm];
void solve(int L,int R,int l,int r)
{
if(L>R||l>r) return;
int cnt1=0,cnt2=0,mid=(l+r)>>1;
for(int i=mid;i>=l;--i)
{
for(int j=m;j;--j)
{
if(w[i][j]=='#') continue;
f[i][j].reset(),f[i][j][j]=(i==mid);
if(i+1<=mid&&w[i+1][j]=='.') f[i][j]|=f[i+1][j];
if(j+1<=m&&w[i][j+1]=='.') f[i][j]|=f[i][j+1];
}
}
for(int i=mid;i<=r;++i)
{
for(int j=1;j<=m;++j)
{
if(w[i][j]=='#') continue;
g[i][j].reset(),g[i][j][j]=(i==mid);
if(i-1>=mid&&w[i-1][j]=='.') g[i][j]|=g[i-1][j];
if(j-1>=1&&w[i][j-1]=='.') g[i][j]|=g[i][j-1];
}
}
for(int i=L;i<=R;++i)
{
if(q[i].x2<mid) q1[++cnt1]=q[i];
if(q[i].x1>mid) q2[++cnt2]=q[i];
if(q[i].x1<=mid&&q[i].x2>=mid)
ans[q[i].id]=(f[q[i].x1][q[i].y1]&g[q[i].x2][q[i].y2]).any();
}
for(int i=1;i<=cnt1;++i) q[L+i-1]=q1[i];
for(int i=1;i<=cnt2;++i) q[L+cnt1+i-1]=q2[i];
solve(L,L+cnt1-1,l,mid-1),solve(L+cnt1,L+cnt1+cnt2-1,mid+1,r);
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;++i) scanf("%s",w[i]+1);
read(t);
for(int i=1;i<=t;++i)
read(q[i].x1),read(q[i].y1),read(q[i].x2),read(q[i].y2),q[i].id=i;
solve(1,t,1,n);
for(int i=1;i<=t;++i) puts(ans[i]?"Yes":"No");
return 0;
}