jzoj100048 【NOIP2017提高A组模拟7.14】紧急撤离 (网格图,分治,bitSet)

题意

给出一个n*m <=500*500的网格图,有一些点有障碍。从一个点只能向下,向右走,询问 q<=6105 :(a,b)是否能到达(c,d)。

分析

询问这么多,如果不是并查集的话一定是预处理了。
考虑直接做,对于每一个询问要nm的时间来暴力。
优化?
枚举一个中间点,然后向两边寻找。
如何配对询问?
将询问用边集数组之类的打到点上。再用O(q)时间询问。
这样我们就切掉了这题 才怪咧,一个点的时间是 O(n2) ,整张图是多少?

等等
不一定经过定点
但是在那一列两侧的一定经过那一列!
考虑分治
将从列 mid 切开,如果不经过中间的就左右两个区间分治搞。
经过中间的如何计算?
暴力还是n3的,考虑dp。发现dp也可以求出一个点(x,y)能到中间点的哪些点。
O(n3)
但如果dp使用bitset压位优化,时间就可以除掉一个常数32。
T(m)=2T(m2)+n2m/32
这样分下去可以分log m次,不难看出每一层都是 n2m/32 ,所以最后时间复杂度就是 O(n2m logm /32+q) ,也就是 O(n3+q) 。 从这里可以发现如果不用bitset那么时间就会多一个log,会T得飞起。
询问需要保证只会被访问一次,所以插入边集数组时有排序姿势。

CODE

#include <iostream>
#include <cstdio>
#include <bitset>
#include <cstring>
#include <algorithm>
#define N 510
using namespace std;
bitset<N> bs[N][N];
int n,m,q,ans[600001],tot;
int next[600001],head[N][N];
bool map[N][N];
struct req{
    int lx,ly,rx,ry,to;
} a[600001],re[600001];
bool cmp(req x,req y) {return x.ry<y.ry;}
void link(int x,int y,const req &r) {
    re[++tot]=r; next[tot]=head[x][y]; head[x][y]=tot;
}
void init() {
    cin>>n>>m;
    char c;
    for (int i=1; i<=n; i++) {
        scanf("\n");
        for (int j=1; j<=m; j++) {
            c=getchar(); map[i][j]=1-(c-'0');
        }
    }
    cin>>q;
    for (int i=1; i<=q; i++) scanf("%d %d %d %d",&a[i].lx,&a[i].ly,&a[i].rx,&a[i].ry),a[i].to=i;
    sort(a+1,a+1+q,cmp);
    for (int i=q; i; i--) link(a[i].lx,a[i].ly,a[i]);
}
void divide(int l,int r) {
    if (l>r) return; int mid=l+r>>1;
    divide(l,mid-1);
    divide(mid+1,r);
    for (int i=1; i<=n; i++) for (int j=l; j<=r; j++) bs[i][j].reset();
    for (int i=1; i<=n; i++) if (map[i][mid]) bs[i][mid].set(i);
    for (int j=mid; j>=l; j--) for (int i=n; i; i--) 
        if (map[i][j]) bs[i][j]|=bs[i+1][j] | bs[i][j+1];
    for (int i=1; i<=n; i++) {
        bs[i][mid].reset();
        if (map[i][mid]) bs[i][mid].set(i);
    }
    for (int j=mid; j<=r; j++) for (int i=1; i<=n; i++)
        if (map[i][j]) {
            bs[i][j]|=bs[i-1][j];
            if (j!=mid) bs[i][j]|=bs[i][j-1];
        }
    for (int i=1; i<=n; i++) for (int j=l; j<=mid; j++) {
        for (int z=head[i][j]; z; z=next[z]) {
            if (re[z].ry>r) break;
            head[i][j]=next[z];
            if (re[z].ry==re[z].ly) ans[re[z].to]=bs[re[z].rx][re[z].ry][re[z].lx];
            else if (re[z].ry==mid) ans[re[z].to]=bs[i][j][re[z].rx];
            else if (j==mid) ans[re[z].to]=bs[re[z].rx][re[z].ry][re[z].lx];
            else if ((bs[i][j] & bs[re[z].rx][re[z].ry]).any()) ans[re[z].to]=1;
        }
    }
}
int main() {
    freopen("3.in","r",stdin);
    freopen("3.out","w",stdout);
    init();
    divide(1,m);
    for (int i=1; i<=q; i++) if (ans[i]) printf("Safe\n"); else printf("Dangerous\n");
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值