题目
题目链接:https://codeforces.com/problemset/problem/232/E
在一个 (n imes m) 的网格上,有一些格子是障碍。给定 (Q) 个询问,每次询问是否能只通过向下走和向右走从格子 ((x_1,y_1)) 走到格子 ((x_2,y_2))。
(n,mleq 500),(Qleq 6 imes 10^5)。
思路
简单粗暴的想法就是直接枚举每一行 (i),然后对于网格中的所有点处理出一个 bitset 表示能不能到第 (i) 行的每一个点。然后枚举询问,如果 (x_1leq ileq x_2) 的话,判断起点与终点的 bitset 是否有交即可。
这样做是 (O(frac{n^2m^2}{omega}+frac{Qn}{omega})) 的。显然过不了。
但是观察到对于一个询问,只需要找一个在 ([x_1,x_2]) 中的行来跑就行了。也就是说,按照上述做法处理完第 (i) 行后,第 (i+1) 行开始就没有必要考虑行不超过 (i) 的询问了。
这启发我们采用分治。对于当前区间 ([l,r]),处理出第 (mid) 行的 bitset,然后对于这个区间内的询问,如果没有跨过第 (mid) 行,就继续分治下去做;否则判断两个 bitset 是否有交。
时间复杂度 (O(frac{n^2mlog n}{omega}+Q(log n+frac{n}{omega})))。
代码
#include <bits/stdc++.h>
#define x1 WYCAKIOI
#define x2 WYCAKIOIOI
#define y1 WYCAKIOIOIOI
#define y2 WYCAKIOIOIOIOI
using namespace std;
const int N=510,M=600010;
int n,m,Q;
char s[N][N];
bool ans[M];
bitset<N> f[N][N],g[N][N];
struct node
{
int x1,y1,x2,y2,id;
};
vector<node> a[N*4];
void solve(int x,int l,int r)
{
if (l>r) return;
for (int i=l;i<=r;i++)
for (int j=1;j<=m;j++)
f[i][j].reset(),g[i][j].reset();
int mid=(l+r)>>1;
for (int i=m;i>=1;i--)
if (s[mid][i]=='.')
f[mid][i][i]=1,f[mid][i]|=f[mid][i+1];
for (int i=1;i<=m;i++)
if (s[mid][i]=='.')
g[mid][i][i]=1,g[mid][i]|=g[mid][i-1];
for (int i=mid-1;i>=l;i--)
for (int j=m;j>=1;j--)
if (s[i][j]=='.')
f[i][j]|=f[i+1][j],f[i][j]|=f[i][j+1];
for (int i=mid+1;i<=r;i++)
for (int j=1;j<=m;j++)
if (s[i][j]=='.')
g[i][j]|=g[i-1][j],g[i][j]|=g[i][j-1];
for (int i=0;i<a[x].size();i++)
{
if (a[x][i].x2<mid) a[x*2].push_back(a[x][i]);
if (a[x][i].x1>mid) a[x*2+1].push_back(a[x][i]);
int x1=a[x][i].x1,y1=a[x][i].y1,x2=a[x][i].x2,y2=a[x][i].y2,id=a[x][i].id;
ans[id]=((f[x1][y1]&g[x2][y2]).count()>0);
}
a[x].clear();
solve(x*2,l,mid-1); solve(x*2+1,mid+1,r);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%s",s[i]+1);
scanf("%d",&Q);
for (int i=1,x1,y1,x2,y2;i<=Q;i++)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
a[1].push_back((node){x1,y1,x2,y2,i});
}
solve(1,1,n);
for (int i=1;i<=Q;i++)
ans[i] ? cout<<"Yes
" : cout<<"No
";
return 0;
}