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

9 篇文章 0 订阅
7 篇文章 0 订阅

        当时已经不(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
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值