JZOJ 100048. 【NOIP2017提高A组模拟7.14】紧急撤离
题目
Description
某日, 敌军对某村落展开攻击,所幸我情报部门提前预知了消息,村民兵武装连夜组织村民快速转移,为此他们需要赶往地道入口。已知村庄形成了 N * M 的方格网络,周围被封锁,无法穿行。其中有些方格没有敌军占领,可以进入,有些方格已经被敌军渗透,不能进入。由于敌军的步步紧逼,民众只能向行或列增大的地方移动:即(x, y) → (x + 1, y)或(x, y) → (x, y + 1)。 机智的 Star 手提笔记本,正和民兵队长商议对策。民兵队长会问你 Q 个问 题,每个问题均类似于坐标(a, b)的村民能否安全到达位于坐标(c, d)的地道(队长是机智的,他的问题总保证起点和终点均安全),你需要赶快写出程序来帮助他。
Input
第 1 行两个整数 N, M;
第 2 ~ n + 1 行给出一个 N * M 的 0/1 矩形(每行可看作一字符串),其中 0 表示安全,1 表示不安全,设第 i + 1 行第 j 列的位置坐标为(i, j)。
第 n + 2 行一个整数 Q;
接下来 Q 行每行四个整数 a、b、c、d,保证坐标合法。
Output
对于每组询问,输出一行“Safe”或“Dangerous”。
Sample Input
7 7
0100000
0000010
0001010
0011000
1000010
0000000
0111001
5
3 3 4 6
6 1 6 7
4 5 6 5
3 3 4 5
4 6 7 6
Sample Output
Dangerous
Safe
Safe
Dangerous
Dangerous
Data Constraint
对于 30%的数据,n, m ≤ 50;
对于另外 20%的数据,n, m ≤ 200,q ≤ 1000;
对于 100%的数据,n, m ≤ 500,q ≤ 600000。
题解
因为询问数过大,显然不能一个个求答案。
对于每组询问,从起点到终点的路径可能较长,所以考虑分治。
对列进行分治。
当前的区间
[
l
,
r
]
[l,r]
[l,r],取中间值
m
i
d
mid
mid。
设
f
[
i
]
[
j
]
[
k
]
=
0
/
1
f[i][j][k]=0/1
f[i][j][k]=0/1为点
(
i
,
j
)
(i,j)
(i,j)是否能走到点
(
k
,
m
i
d
)
(k,mid)
(k,mid),
g
[
i
]
[
j
]
[
k
]
=
0
/
1
g[i][j][k]=0/1
g[i][j][k]=0/1为点
(
k
,
m
i
d
)
(k,mid)
(k,mid)是否能走到点
(
i
,
j
)
(i,j)
(i,j),
简单循环实现动态规划。
由于这样时间复杂度稍大,需要优化。
f
,
g
f,g
f,g数组一个位置可以压缩多个状态,可以压
30
30
30位(二进制十进制),在转移时直接通过“或”运算(某一位上同假为假,否则为真)即可实现时间的优化。
注意
j
=
m
i
d
j=mid
j=mid时要特殊处理。
算答案时分两种情况讨论:
一、起点和终点分别在
m
i
d
mid
mid列的两边(或在
m
i
d
mid
mid列上)
如果
(
a
,
b
)
(a,b)
(a,b)能走到
m
i
d
mid
mid列的某一位置,且该位置可以走到
(
c
,
d
)
(c,d)
(c,d),那么就是可以的。
用已算好的
f
,
g
f,g
f,g数组,通过“与”运算(某一位上同真为真,否则为假)即可快速实现。
二、起点和终点分别在
m
i
d
mid
mid列的同一边
继续分治
[
l
,
m
i
d
−
1
]
[l,mid-1]
[l,mid−1]和
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r],这样每组询问总会有机会计算到。
这样我们先把询问排序,即可快速实现上述的分类。
还需要加以个优化,因为每组询问可能被多个
m
i
d
mid
mid计算到,所以可以每计算一组询问就删去,用双向链表维护。
代码
#include<cstdio>
#include<cstring>
using namespace std;
int a[510][510],n,m,ans[600010],h[510],e[31];
struct
{
int a,b,c,d,id,l,r;
}b[600010];
int f[510][510][18],g[510][510][18];
void qsort(int l,int r)
{
int x=l,y=r,mid=b[(l+r)/2].b;
while(x<=y)
{
while(b[x].b<mid) x++;
while(b[y].b>mid) y--;
if(x<=y) b[0]=b[x],b[x]=b[y],b[y]=b[0],x++,y--;
}
if(x<r) qsort(x,r);
if(y>l) qsort(l,y);
}
void dfs(int l,int r)
{
int mid=(l+r)/2,i,j,k;
for(i=1;i<=n;i++)
for(j=l;j<=mid;j++)
for(k=1;k<=n/30+1;k++) f[i][j][k]=0;
for(i=1;i<=n;i++)
for(j=mid;j<=r;j++)
for(k=1;k<=n/30+1;k++) g[i][j][k]=0;
for(i=n;i>0;i--) if(!a[i][mid])
{
k=(i-1)/30+1;int k1=(i-1)%30;
f[i][mid][k]+=e[k1];
for(j=k;j<=n/30+1;j++)
f[i][mid][j]=f[i][mid][j]|f[i+1][mid][j];
}
for(i=1;i<=n;i++) if(!a[i][mid])
{
k=(i-1)/30+1;int k1=(i-1)%30;
g[i][mid][k]+=e[k1];
for(j=1;j<=k;j++)
g[i][mid][j]=g[i][mid][j]|g[i-1][mid][j];
}
for(i=n;i>0;i--)
for(j=mid-1;j>=l;j--) if(!a[i][j])
for(k=1;k<=n/30+1;k++)
{
f[i][j][k]=f[i+1][j][k]|f[i][j+1][k];
}
for(i=1;i<=n;i++)
for(j=mid+1;j<=r;j++) if(!a[i][j])
for(k=1;k<=n/30+1;k++)
{
g[i][j][k]=g[i-1][j][k]|g[i][j-1][k];
}
for(i=h[mid];i>0;i=b[i].l) if(ans[b[i].id]==-1)
{
if(b[i].d<mid) continue;
ans[b[i].id]=0;
for(k=1;k<=n/30+1;k++) if(f[b[i].a][b[i].b][k]&g[b[i].c][b[i].d][k]) ans[b[i].id]=1;
b[b[i].l].r=b[i].r;
b[b[i].r].l=b[i].l;
}
if(l<mid) dfs(l,mid-1);
if(mid<r) dfs(mid+1,r);
}
int main()
{
int i,j,q;
char c;
scanf("%d%d
",&n,&m);
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
scanf("%c",&c);
a[i][j]=c-'0';
}
scanf("
");
}
scanf("%d",&q);
for(i=1;i<=q;i++) scanf("%d%d%d%d",&b[i].a,&b[i].b,&b[i].c,&b[i].d),b[i].id=i,ans[i]=-1;
qsort(1,q);
b[0].r=1,b[q+1].l=q;
for(i=1;i<=q;i++) b[i].l=i-1,b[i].r=i+1;
j=1;
for(i=1;i<=m;i++)
{
h[i]=h[i-1];
while(j<=q&&b[j].b<=i) h[i]=j,j++;
}
e[0]=1;for(i=1;i<=30;i++) e[i]=e[i-1]*2;
dfs(1,m);
for(i=1;i<=q;i++) if(ans[i]) printf("Safe
"); else printf("Dangerous
");
return 0;
}