100048. 【NOIP2017提高A组模拟7.14】紧急撤离

1 篇文章 0 订阅
题目描述

某日, 敌军对某村落展开攻击,所幸我情报部门提前预知了消息,村民兵武装连夜组织村民快速转移,为此他们需要赶往地道入口。已知村庄形成了 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,m500,q6105

题目分析
  • 这一道题如果在线做的话比较难,所以考虑离线做。
  • 说到离线做,有一个东西肯定不得不想到(特别是在这么大的数据下)——分治!
  • (题外话)众所周知,分治可以使时间复杂度降到 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值