[uoj184][loj2090][bzoj4456]「ZJOI2016」旅行者 网格图多源多汇最短路 题解

17 篇文章 0 订阅
6 篇文章 0 订阅

题目描述

小Y来到了一个新的城市旅行。她发现了这个城市的布局是网格状的,也就是有 nn 条从东到西的道路和 mm 条从南到北的道路,这些道路两两相交形成 n×mn×m 个路口 (i,j)(1≤i≤n,1≤j≤m)(i,j)(1≤i≤n,1≤j≤m)。

她发现不同的道路路况不同,所以通过不同的路口需要不同的时间。通过调查发现,从路口 (i,j)(i,j) 到路口 (i,j+1)(i,j+1) 需要时间 ri,jri,j,从路口 (i,j)(i,j) 到路口 (i+1,j)(i+1,j) 需要时间 ci,jci,j。注意这里的道路是双向的,也就是从路口 (i,j+1)(i,j+1) 到路口 (i,j)(i,j) 需要时间同样是 ri,jri,j。

小Y有 qq 个询问,她想知道从路口 (x1,y1)(x1,y1) 到路口 (x2,y2)(x2,y2) 最少需要花多少时间。

输入格式

第一行包含 2 个正整数 n,mn,m ,表示城市的大小。

接下来 nn 行,每行包含 m−1m−1 个整数,第 ii 行第 jj 个正整数表示从一个路口到另一个路口的时间 ri,jri,j。

接下来 n−1n−1 行,每行包含 mm 个整数,第 ii 行第 jj 个正整数表示从一个路口到另一个路口的时间 ci,jci,j。

接下来一行,包含1个正整数 qq ,表示小Y的询问个数。

接下来 qq 行,每行包含4个正整数 x1,y1,x2,y2x1,y1,x2,y2,表示两个路口的位置。

输出格式

输出共 qq 行,每行包含一个整数表示从一个路口到另一个路口最少需要花的时间。

样例输入

2 2
2
3
6 4
2
1 1 2 2
1 2 2 1

样例输出

6
7

题解

按照网格分治,x轴和y轴哪边大做哪边。
取中间那条线,那么询问在左右两边(或上下两边)的点一定会经过中间这条线上的点。
那么枚举中间这条线上的点,dij求最短路后枚举询问取min。
如果有询问的两个点在同一边的,继续分治下去即可。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 20100
#define P(x,y) ((x-1)*m+y)
using namespace std;
int n,m,a[N][4],Q,f[N],ans[N*5];
int fx[4][2]={0,-1,-1,0,0,1,1,0};
struct node{
	int q1,w1,q2,w2,z;
}q[N*5],ql[N*5],qr[N*5];
struct note{
	int x,y,d;
	friend bool operator < (note a,note b){return a.d>b.d;}
};
priority_queue<note> d;
int read()
{
	char c=getchar();int x=0;
	for(;c<'0'||c>'9';c=getchar());
	for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-48;
	return x;
}
void dij(int xs,int ys,int l1,int r1,int l2,int r2)
{
	priority_queue<note> d;
	fo(i,l1,r1) fo(j,l2,r2) f[P(i,j)]=0xfffffff;
	d.push((note){xs,ys,0});
	f[P(xs,ys)]=0;
	while(!d.empty())
	{
		note jy=d.top();
		d.pop();
		int p=P(jy.x,jy.y);
		if(jy.d>f[p]) continue;
		fo(i,0,3)
		{
			int x=jy.x+fx[i][0],y=jy.y+fx[i][1];
			if(x<l1||x>r1||y<l2||y>r2) continue;
			if(f[p]+a[p][i]<f[P(x,y)])
			{
				f[P(x,y)]=f[p]+a[p][i];
				d.push((note){x,y,f[P(x,y)]});
			}
		}
	}
}
void solve(int l1,int r1,int l2,int r2,int l,int r)
{
	if(l>r) return;
	if(r1-l1>r2-l2)
	{
		int mid=(l1+r1)>>1;
		fo(i,l2,r2)
		{
			dij(mid,i,l1,r1,l2,r2);
			fo(j,l,r) ans[q[j].z]=min(ans[q[j].z],f[P(q[j].q1,q[j].w1)]+f[P(q[j].q2,q[j].w2)]);
		}
		int tl=0,tr=0;
		fo(i,l,r)
		{
			if(q[i].q1<=mid&&q[i].q2<=mid) ql[++tl]=q[i];
			if(q[i].q1>mid&&q[i].q2>mid) qr[++tr]=q[i];
		}
		fo(i,1,tl) q[i+l-1]=ql[i];
		fo(i,1,tr) q[r-tr+i]=qr[i];
		if(l1==r1) return;
		solve(l1,mid,l2,r2,l,l+tl-1);
		solve(mid+1,r1,l2,r2,r-tr+1,r);
	}
	else
	{
		int mid=(l2+r2)>>1;
		fo(i,l1,r1)
		{
			dij(i,mid,l1,r1,l2,r2);
			fo(j,l,r) ans[q[j].z]=min(ans[q[j].z],f[P(q[j].q1,q[j].w1)]+f[P(q[j].q2,q[j].w2)]);
		}
		int tl=0,tr=0;
		fo(i,l,r)
		{
			if(q[i].w1<=mid&&q[i].w2<=mid) ql[++tl]=q[i];
			if(q[i].w1>mid&&q[i].w2>mid) qr[++tr]=q[i];
		}
		fo(i,1,tl) q[i+l-1]=ql[i];
		fo(i,1,tr) q[r-tr+i]=qr[i];
		if(l2==r2) return;
		solve(l1,r1,l2,mid,l,l+tl-1);
		solve(l1,r1,mid+1,r2,r-tr+1,r);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	fo(i,1,n) fo(j,1,m-1) a[P(i,j)][2]=a[P(i,j+1)][0]=read();
	fo(i,1,n-1) fo(j,1,m) a[P(i,j)][3]=a[P(i+1,j)][1]=read();
	scanf("%d",&Q);
	fo(i,1,Q) scanf("%d%d%d%d",&q[i].q1,&q[i].w1,&q[i].q2,&q[i].w2),q[i].z=i;
	memset(ans,127,sizeof(ans));
	solve(1,n,1,m,1,Q);
	fo(i,1,Q) printf("%d\n",ans[i]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值