关闭

uoj184 bzoj 4456: [Zjoi2016]旅行者 分治+最短路

标签: 分治最短路
2738人阅读 评论(5) 收藏 举报
分类:

        当时已经不(fang)想(qi)做(zhi)题(liao)了,写了个分块搞(pian)了50分。

        实际上分块和分治的思想是差不多的,就直接讲分治吧。。

        首先转离线操作,然后对于某一个矩形区间x∈[lx,rx],y∈[ly,ry],然后要求出所有源点和汇点都在其中的询问,且路径不超出所在区间的答案。不妨设rx-lx>ly-ty,那么对x坐标进行分治,即将这个区间分成两块,那么对于某一个询问,有两种情况:

       1.如果询问的起点和终点在两个不同的块,那么一定会经过中轴线上的一点;

       2.如果在同一块,那么有可能经过中轴线;也有可能路径只在那一块中,就可以递归分治了;

       那么对于某一块,求出中轴线到所在块的所有点的距离,更新一下答案;然后递归分治。

       考虑用dijkstra+heap跑最短路,那么就大概是O(N^1.5logN)的(因为思想和kd-tree是差不多的吧所以时间也一样UPD:应该是dijkstra的复杂度)。本地测试后两个点dijkstra+heap的时间接近spfa的一半,但是为什么uoj上jiry_2的spfa(可能是?)跑的飞快?

AC代码如下:

#include<iostream>
#include<cstdio>
#define inf 1000000000
#define getp(x,y) (x-1)*n+y
#define N 100005
using namespace std;

int n,m,cnt,tot,fst[N],pnt[N],len[N],nxt[N],q[N],id[N],d[N],ans[N];
struct node{ int x1,y1,x2,y2,id; }a[N],b[N];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
void add(int x,int y,int z){
	pnt[++tot]=y; len[tot]=z; nxt[tot]=fst[x]; fst[x]=tot;
}
void goup(int x){
	int y=q[x],t=d[y];
	for (; x>1 && t<d[q[x>>1]]; x>>=1){
		q[x]=q[x>>1]; id[q[x]]=x;
	}
	q[x]=y; id[y]=x;
}
void godown(int x){
	int y=x<<1,z=q[x],t=d[z];
	if (y<cnt && d[q[y|1]]<d[q[y]]) y|=1;
	while (y<=cnt && d[q[y]]<t){
		q[x]=q[y]; id[q[x]]=x;
		x=y; y<<=1; if (y<cnt && d[q[y|1]]<d[q[y]]) y|=1;
	}
	q[x]=z; id[z]=x;
}
void dijk(int sta,int x1,int x2,int y1,int y2){
	int i,j,x,y,p,u,v; cnt=1;
	q[1]=sta; id[sta]=1; d[sta]=0;
	for (i=x1; i<=x2; i++)
		for (j=y1; j<=y2; j++){
			x=getp(i,j);
			if (x!=sta){ d[x]=inf; q[++cnt]=x; id[x]=cnt; }
		}
	while (cnt){
		x=q[1]; q[1]=q[cnt--]; id[q[1]]=1; godown(1);
		for (p=fst[x]; p; p=nxt[p]){
			y=pnt[p]; u=(y-1)/n+1; v=(y-1)%n+1;
			if (u>=x1 && u<=x2 && v>=y1 && v<=y2 && d[x]+len[p]<d[y]){
				d[y]=d[x]+len[p]; goup(id[y]);
			}
		}
	}
}
void solve(int x1,int x2,int y1,int y2,int l,int r){
	if (l>r) return; int i,j,k,mid;
	if (x2-x1>y2-y1){
		mid=(x1+x2)>>1;
		for (i=y1; i<=y2; i++){
			dijk(getp(mid,i),x1,x2,y1,y2);
			for (j=l; j<=r; j++)
				ans[a[j].id]=min(ans[a[j].id],d[getp(a[j].x1,a[j].y1)]+d[getp(a[j].x2,a[j].y2)]);
		}
		j=l-1; k=r+1;
		for (i=l; i<=r; i++)
			if (a[i].x1<mid && a[i].x2<mid) b[++j]=a[i]; else
			if (a[i].x1>mid && a[i].x2>mid) b[--k]=a[i];
		for (i=l; i<=j; i++) a[i]=b[i]; solve(x1,mid-1,y1,y2,l,j);
		for (i=k; i<=r; i++) a[i]=b[i]; solve(mid+1,x2,y1,y2,k,r);
	} else{
		mid=(y1+y2)>>1;
		for (i=x1; i<=x2; i++){
			dijk(getp(i,mid),x1,x2,y1,y2);
			for (j=l; j<=r; j++)
				ans[a[j].id]=min(ans[a[j].id],d[getp(a[j].x1,a[j].y1)]+d[getp(a[j].x2,a[j].y2)]);
		}
		j=l-1; k=r+1;
		for (i=l; i<=r; i++)
			if (a[i].y1<mid && a[i].y2<mid) b[++j]=a[i]; else
			if (a[i].y1>mid && a[i].y2>mid) b[--k]=a[i];
		for (i=l; i<=j; i++) a[i]=b[i]; solve(x1,x2,y1,mid-1,l,j);
		for (i=k; i<=r; i++) a[i]=b[i]; solve(x1,x2,mid+1,y2,k,r);
	}
}
int main(){
	m=read(); n=read(); int i,j,x;
	for (i=1; i<=m; i++)
		for (j=1; j<n; j++){
			x=read();
			add(getp(i,j),getp(i,j+1),x); add(getp(i,j+1),getp(i,j),x);
		}
	for (i=1; i<m; i++)
		for (j=1; j<=n; j++){
			x=read();
			add(getp(i,j),getp(i+1,j),x); add(getp(i+1,j),getp(i,j),x);
		}
	int cas=read();
	for (i=1; i<=cas; i++){
		a[i].x1=read(); a[i].y1=read(); a[i].x2=read(); a[i].y2=read();
		a[i].id=i; ans[i]=inf;
	}
	solve(1,m,1,n,1,cas);
	for (i=1; i<=cas; i++) printf("%d\n",ans[i]);
	return 0;
}


by lych

2016.3.25

1
0
查看评论

BZOJ4456 [Zjoi2016]旅行者

好神的分治做法 我们把矩形顺着比较短的一边从中间切成两半,从这条中线上每个点做一次到这个矩形内每个点的dijkstra,把两个端点都在这个矩形里的询问用中线上每个点到两个端点的最短路更新,然后分治两半边 复杂度分析:瞎YY一下我们知道这个算法每次更新答案要做比较小的一边的边长次dijkstra,...
  • neither_nor
  • neither_nor
  • 2016-06-22 14:21
  • 961

4456: [Zjoi2016]旅行者 分治+最短路

UOJ跑不过。。BZOJ勉强过啦。 首先离线,然后考虑像K-D tree一样进行分治,比如当前的矩形区域为(x1,y1)..(x2,y2),那么考虑点在区域内的询问,我们将矩形划分为两部分(划分较长的一边),然后考虑两种情况: 如果询问的两点在分界线的两边,那么最短路一定跨过分界线。 否则也有...
  • Phenix_2015
  • Phenix_2015
  • 2016-04-15 17:06
  • 641

uoj184 bzoj 4456: [Zjoi2016]旅行者 分治+最短路

当时已经没心情做题(qi liao)了,写了个分块搞(pian)了50分。         实际上分块和分治的思想是差不多的,就直接讲分治吧。。         首先转离线操作,然后对于某一个矩形区间x∈[lx,rx],...
  • lych_cys
  • lych_cys
  • 2016-03-25 20:39
  • 2738

4456: [Zjoi2016]旅行者|分治+最短路

每次将矩形划分成两个部分,枚举中间点跑最短路更新答案,不断递归分治#include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio...
  • ws_yzy
  • ws_yzy
  • 2016-04-18 15:13
  • 973

【bzoj4456】[Zjoi2016]旅行者

这真是一道最短路吼题啊 http://www.lydsy.com/JudgeOnline/problem.php?id=4456 zj的题就是质量高,最短路这种看起来简单的东西也可以出成一道吼题(gd的题就是质量高,暴搜剪枝这种东西都能骗人写插头dp 这道题显然是一道最短路,但是看似有点难做,...
  • orzGEOTCBRL
  • orzGEOTCBRL
  • 2017-01-18 20:13
  • 470

UOJ 184 [ZJOI2016]旅行者

离线分治,最短路这一篇说得很清楚了,时间复杂度也有分析: http://blog.csdn.net/neither_nor/article/details/51733997UOJ上的点实在是恶心。普通最短路不能过,发现每一次分治下去要连续跑最短路,前一次最短路的结果可以帮助下一次。然后套上堆优化d...
  • ziqian2000
  • ziqian2000
  • 2016-10-04 14:35
  • 149

[分治 最短路] BZOJ 4456 [Zjoi2016]旅行者

所有询问离线 把平面分成两块 如果询问两点在同一边 可以不穿过中线 也可以穿过 如果询问两点在不同边 必须穿过 那么我们就可以用中线上所有点更新下 然后递归 卡常数?#include<cstdio> #include<cstdlib> #include<a...
  • u014609452
  • u014609452
  • 2017-03-21 23:40
  • 271

[分治 + 最短路] BZOJ4456: [Zjoi2016]旅行者

题意给出一个n行m列的网格图,每个点与上下左右相邻4个点有双向边。 询问Q次,每次求两点最短路。 n*m<=20000,Q<=100000题解分治这个考虑方向还是容易想到的。 就是每次选长的那一边切成两半。 对于一个询问,若两点在异侧,则最短路一定经过中线。若两点在同侧,最短路可...
  • CHHNZ
  • CHHNZ
  • 2017-06-05 21:32
  • 240

bzoj 4456: [Zjoi2016]旅行者 分治+最短路

题意 小Y来到了一个新的城市旅行。她发现了这个城市的布局是网格状的,也就是有n条从东到西的道路和m条从南到北的道路,这些道路两两相交形成n×m个路口 (i,j)(1≤i≤n,1≤j≤m)。她发现不同的道路路况不同,所以通过不同的路口需要不同的时间。通过调查发现,从路口(i,j)到路口(i...
  • qq_33229466
  • qq_33229466
  • 2018-01-12 21:29
  • 61

bzoj 4456: [Zjoi2016]旅行者

分治+最短路 离线,分治,每次查询起点终点都在[x1,x2][y1,y2]内的答案。接下来讨论x2-x1>y2-y1的情况,反之类比即可。 现在我们要计算的是路径范围在这个矩形之内,且路径经过中轴线的答案。 枚举中轴线上的每个点,计算它到矩形内的点的最短路,然后用dis[a]+dis[b...
  • heheda_is_an_OIer
  • heheda_is_an_OIer
  • 2016-04-14 19:59
  • 716
    个人资料
    • 访问:310335次
    • 积分:7144
    • 等级:
    • 排名:第3779名
    • 原创:403篇
    • 转载:0篇
    • 译文:0篇
    • 评论:109条
    文章分类
    最新评论