题目描述
某日, 敌军对某村落展开攻击,所幸我情报部门提前预知了消息,村民兵武装连夜组织村民快速转移,为此他们需要赶往地道入口。已知村庄形成了 N * M 的方格网络,周围被封锁,无法穿行。其中有些方格没有敌军占领,可以进入,有些方格已经被敌军渗透,不能进入。由于敌军的步步紧逼,民众只能向行或列增大的地方移动:即(x, y) → (x + 1, y)或(x, y) → (x, y + 1)。 机智的 Star 手提笔记本,正和民兵队长商议对策。民兵队长会问你 Q 个问 题,每个问题均类似于坐标(a, b)的村民能否安全到达位于坐标(c, d)的地道(队长是机智的,他的问题总保证起点和终点均安全),你需要赶快写出程序来帮助他。
题目描述简化版
给定一个图,一些点能走,一些点不能走。每次只能向右或向下走,给q个询问,求能否从一个点到达另一个点。
数据范围
n , m ≤ 500 , q ≤ 6 ∗ 1 0 5 n,m \le 500,q \le 6*10^5 n,m≤500,q≤6∗105
题目分析
- 这一道题如果在线做的话比较难,所以考虑离线做。
- 说到离线做,有一个东西肯定不得不想到(特别是在这么大的数据下)——分治!
- (题外话)众所周知,分治可以使时间复杂度降到 l o g log log级别,所以我们不妨来试一试。
- 首先让我们来想一想,如果数据足够小,能用什么方法。——不错,用Floyd。
- 其实,在分治的时候,虽然直接用Floyd肯定会超时,可我们不妨继续沿用它的思想——分治中转点。
- 但如果直接分治点的话,恐怕会比较麻烦,所以不妨整列整列分治,对于跨过这一列的询问,我们用类似于Floyd的思想枚举这一列上的点作为中转点看能否联通。
- 设当前分治到的列为第mid列,我们在这一列左右各做一个DP,就以左边为例,右边类似。
- 我们设 f [ i ] [ j ] [ k ] = 0 / 1 f[i][j][k]=0/1 f[i][j][k]=0/1表示第i行第j列的点能否到达第k行第mid列的点。
- 显然从它的右边那个点和它的下边那个点转移
- 右边的 g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k]则从它的左边和它的上边转移
- 最后对于一个跨越第mid列的询问,我们判断第mid列上的每一个点是否能够连接询问的那两个点即可
- 时间复杂度为 O ( n 3 l o g 2 n ) O(n^3log_2n) O(n3log2n),显然很难过去。
- 但我们发现两个DP的值都只有0和1,所以我们不妨压位,把30个01压成一个int级别的数
- 然后时间复杂度就是 O ( n 3 l o g 2 n 30 ) O(\frac{n^3log_2n}{30}) O(30n3log2n),勉强可以过去
代码
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
const int si=30,ze=17;
char s[510];
int f[510][510][19],p[510][510][19];
struct node{
int x1,y1,x2,y2,id;
}tr[610000];
bool g[510],bk[510][510];int c[510],d[510],h[32];
bool ans[610000];
int cmp(const void *xx,const void *yy){
node n1=*(node *)xx;
node n2=*(node *)yy;
return n1.y1-n2.y1;
}
int cmp2(const void *xx,const void *yy){
node n1=*(node *)xx;
node n2=*(node *)yy;
return n1.id-n2.id;
}
inline void read(int &x){
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
x=0;
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
int n,m;
void solve(int l,int r){
int mid=(l+r)/2;
if(l<r) solve(l,mid-1),solve(mid+1,r);
for(int i=l;i<=mid;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=ze;k++) f[j][i][k]=0;
for(int i=mid;i<=r;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=ze;k++) p[j][i][k]=0;
for(int i=1;i<=n;i++){
int t1=(i-1)/si+1,t2=(i-1)%si+1;
if(bk[i][mid]) f[i][mid][t1]=p[i][mid][t1]=h[t2];
else f[i][mid][t1]=p[i][mid][t1]=0;
for(int j=1;j<=ze;j++) if(j!=t1) f[i][mid][j]=p[i][mid][j]=0;
}
for(int i=n;i>=1;i--)
for(int j=mid;j>=l;j--)
for(int k=1;k<=ze;k++){
int sum=0;
if(j<mid&&bk[i][j+1]) sum|=f[i][j+1][k];
if(i<n&&bk[i+1][j]) sum|=f[i+1][j][k];
f[i][j][k]|=sum;
}
for(int i=1;i<=n;i++)
for(int j=mid;j<=r;j++)
for(int k=1;k<=ze;k++){
int sum=0;
if(j>mid&&bk[i][j-1]) sum|=p[i][j-1][k];
if(i>1&&bk[i-1][j]) sum|=p[i-1][j][k];
p[i][j][k]|=sum;
}
for(int i=l;i<=mid;i++){
if(g[i]){
for(int j=c[i];j<=d[i];j++)
if(tr[j].y2>=mid&&tr[j].y2<=r){
ans[tr[j].id]=false;
int x1=tr[j].x1,y1=tr[j].y1,x2=tr[j].x2,y2=tr[j].y2;
for(int k=1;k<=ze;k++){
if((f[x1][y1][k]&p[x2][y2][k])!=0) {ans[tr[j].id]=true;break;}
}
}
}
}
}
int main()
{
scanf("%d%d\n",&n,&m);
memset(bk,false,sizeof(bk));
h[1]=1;for(int i=2;i<=si;i++) h[i]=h[i-1]*2;
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
bk[i][j]=1-(s[j]-'0');
}
int q;scanf("%d",&q);
for(int i=1;i<=q;i++)
read(tr[i].x1),read(tr[i].y1),read(tr[i].x2),read(tr[i].y2),tr[i].id=i;
qsort(tr+1,q,sizeof(node),cmp);
memset(g,false,sizeof(g));tr[0].y1=0;
for(int i=1;i<=q;i++){
if(i==1||tr[i].y1!=tr[i-1].y1){
g[tr[i].y1]=true;
d[tr[i-1].y1]=i-1;c[tr[i].y1]=i;
}
}
d[tr[q].y1]=q;
solve(1,m);
for(int i=1;i<=q;i++){
if(ans[i]) cout<<"Safe\n";
else cout<<"Dangerous\n";
}
return 0;
}