【ZJOI2016】旅行者 - 题解

【ZJOI2016】旅行者

题意:

给你一个网格图以及图上的边权,多个询问,求网格图内两点的最短路径。点数不多于 $ 20000 $ ,询问不超过 $ 100000 $ ,边权不超过 $ 10000 $ 。

题解:

算法一:

对于每次询问暴力跑两点最短路,听说是网格图, $ Spfa $ 再见, $ Dijkstra $ 加堆优化飞起。得分 $ 20 $ 。

算法二:

在线做法已经很难再快了,考虑离线。这是一个网格图,所以我们考虑分治,每次处理左上端点为 $ (x1, y1) $ 右下端点为 $ (x2, y2) $ 矩形且询问的点都在范围内的询问。然后我们要将这个矩形切开,当然最好是从中间切开。然后我们考虑当前所有询问经过中线的答案是否比当前答案更优,然后将询问分类并进行分治(跨过中线的两个点答案已经算好,其他的还需递归分治)。我们发现将矩形较长边从中间切开较优。时间复杂度玄学(听说是 $ O(n \sqrt{n} \log{n}) $ 的)。
咦,被卡常了?得分 $ 50 $ ~ $ 100 $ 分。

算法三:

考虑剪枝。如果当前矩形内询问只有一两个时直接暴力查询即可,其实也没快多少

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=20010,M=100010,INF=2e9;
const int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
int n,m,k,e[N][4];
struct Q { int l1,r1,l2,r2,id; }; Q q[M],ta[M],tb[M];
int ans[M],dis[N];
struct D {
	int id,w; D(int Id=0,int W=0):id(Id),w(W) {}
	inline bool operator<(const D &yy)const { return w>yy.w; }
};priority_queue <D> h;

int read() {
	register int tmp=0;register char c=getchar();
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';tmp=(tmp<<1)+(tmp<<3)+(c^48),c=getchar());
	return tmp;
}
inline int Min(int x,int y) { return x<y? x:y; }
inline int g(int x,int y) { return (x-1)*m+y; }
void Dijkstra(int l1,int r1,int l2,int r2,int s) {
	for(int i=l1;i<=l2;i++) for(int j=r1;j<=r2;j++) dis[g(i,j)]=INF;
	dis[s]=0,h.push(D(s,0));
	for(;!h.empty();) {
		D u=h.top(); h.pop(); if(u.w!=dis[u.id]) continue;
		int x=(u.id-1)/m+1,y=(u.id-1)%m+1,tx,ty,w;
		for(int i=0;i<4;i++) {
			tx=x+dir[i][0],ty=y+dir[i][1],w=e[u.id][i];
			if(tx<l1||tx>l2||ty<r1||ty>r2) continue;
			if(dis[g(tx,ty)]>u.w+w)
				dis[g(tx,ty)]=u.w+w,h.push(D(g(tx,ty),dis[g(tx,ty)]));
		}
	}
}
void solve(int l1,int r1,int l2,int r2,int l,int r) {
	if(l1>l2||r1>r2||l>r) return ;
	if(l==r) {
		Dijkstra(l1,r1,l2,r2,g(q[l].l1,q[l].r1));
		ans[q[l].id]=Min(dis[g(q[l].l2,q[l].r2)],ans[q[l].id]); return ;
	}
	if(l2-l1<=r2-r1) {
		int mid=(r1+r2)/2;
		for(int i=l1;i<=l2;i++) {
			Dijkstra(l1,r1,l2,r2,g(i,mid));
			for(int j=l;j<=r;j++)
				ans[q[j].id]=Min(ans[q[j].id],
				dis[g(q[j].l1,q[j].r1)]+dis[g(q[j].l2,q[j].r2)]);
		}
		int la=0,lb=0;
		for(int i=l;i<=r;i++) {
			if(q[i].r1<=mid&&q[i].r2<=mid) ta[++la]=q[i];
			if(q[i].r1>mid&&q[i].r2>mid) tb[++lb]=q[i];
		}
		for(int i=1;i<=la;i++) q[i+l-1]=ta[i];
		for(int i=1;i<=lb;i++) q[r-i+1]=tb[i];
		if(r1==r2) return ;
		solve(l1,r1,l2,mid,l,l+la-1),solve(l1,mid+1,l2,r2,r-lb+1,r);
	}
	else {
		int mid=(l1+l2)/2;
		for(int i=r1;i<=r2;i++) {
			Dijkstra(l1,r1,l2,r2,g(mid,i));
			for(int j=l;j<=r;j++)
				ans[q[j].id]=Min(ans[q[j].id],
				dis[g(q[j].l1,q[j].r1)]+dis[g(q[j].l2,q[j].r2)]);
		}
		int la=0,lb=0;
		for(int i=l;i<=r;i++) {
			if(q[i].l1<=mid&&q[i].l2<=mid) ta[++la]=q[i];
			if(q[i].l1>mid&&q[i].l2>mid) tb[++lb]=q[i];
		}
		for(int i=1;i<=la;i++) q[i+l-1]=ta[i];
		for(int i=1;i<=lb;i++) q[r-i+1]=tb[i];
		if(l1==l2) return ;
		solve(l1,r1,mid,r2,l,l+la-1),solve(mid+1,r1,l2,r2,r-lb+1,r);
	}
}
int main() {
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<m;j++) e[g(i,j)][1]=e[g(i,j+1)][3]=read();
	for(int i=1;i<n;i++)
		for(int j=1;j<=m;j++) e[g(i,j)][0]=e[g(i+1,j)][2]=read();
	k=read();
	for(int i=1;i<=k;i++) {
		q[i].id=i,ans[i]=INF;
		q[i].l1=read(),q[i].r1=read(),q[i].l2=read(),q[i].r2=read();
	}
	solve(1,1,n,m,1,k); for(int i=1;i<=k;i++) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2019-01-26 10:31  daniel14311531  阅读(317)  评论(0编辑  收藏  举报