把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4456】[ZJOI2016] 旅行者(神奇的分治)

点此看题面

大致题意: 一张网格图,每相邻两个格子之间都有一条带权无向边,每次询问两点间的最短路。

套路?

听说有一个套路:网格图的题目一般都可以用分治去搞。

emmm 几乎从没见过网格图的题,当然更不知道有这样的套路啦。。。

分治

考虑每次处理一个矩形区域,我们连接长边的中点,就可以把矩形分成两部分(连接长边中点是为了使中线长尽量短,等于短边)。

然后就会发现,询问被分成了两类:

  • 两点在不同部分:显然它们的最短路必然经过中线上至少一个点。那我们只要枚举中线上的点,分别求出它只经过当前矩形内的点(因为矩形外的点肯定已经在之前被处理过了)到所有点的最短路,就可以求出答案了。
  • 两点在同一部分:它们的最短路可能不经过中线(可以递归搞),可能经过中线(和上面一样的方式搞)。

递归时就是把两点在同一部分的询问分别分到中线两边的矩形中,如果你写过整体二分就会发现两者是非常相似的。

由上可见,其实知道套路之后,这题就非常简单了。但我显然不知道有这种神奇的套路啊。

至于复杂度,令\(S=nm\),显然短边最长也就是\(O(\sqrt S)\),而分治复杂度是\(O(logS)\)的,因此总复杂度为\(O(S\sqrt Slog S)\)

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define NM 20000
#define Q 100000
#define INF 2147483647
#define P(x,y) (((x)-1)*m+(y))
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
const int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
int n,m,Qt,w[NM+5][4],ans[Q+5];struct Qry {int p,x1,y1,x2,y2;}q[Q+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
		#undef D
}F;
class Dijkstra
{
	private:
		#define mp make_pair
		#define fir first
		#define sec second
		int d[NM+5],vis[NM+5];typedef pair<int,int> Pr;
		priority_queue<Pr,vector<Pr>,greater<Pr> > q;
	public:
		I int operator [] (CI x) Con {return d[x];}
		I void Dij(CI sx,CI sy,CI l1,CI r1,CI l2,CI r2)//Dijkstra求单源最短路
		{
			RI i,j;for(i=l1;i<=r1;++i) for(j=l2;j<=r2;++j) d[P(i,j)]=INF,vis[P(i,j)]=0;//初始化
			q.push(mp(d[P(sx,sy)]=0,P(sx,sy)));RI x,y,nx,ny;Pr k;W(!q.empty())
			{
				if(k=q.top(),q.pop(),vis[k.sec]) continue;vis[k.sec]=1;
				for(!(y=k.sec%m)&&(y=m),x=(k.sec-y)/m+1,i=0;i^4;++i)//转化回坐标,枚举四个方向
				{
					if((nx=x+dx[i])<l1||nx>r1||(ny=y+dy[i])<l2||ny>r2) continue;//若不在范围内
					d[P(nx,ny)]>k.fir+w[P(x,y)][i]&&
						(q.push(mp(d[P(nx,ny)]=k.fir+w[P(x,y)][i],P(nx,ny))),0);
				}
			}
		}
}D;
Qry sl[Q+5],sr[Q+5];I void Solve(CI l1,CI r1,CI l2,CI r2,CI l,CI r)//分治
{
	RI i,j,mid,tl=0,tr=0;if(l>r||(l1==r1&&l2==r2)) return;//边界,无须再处理
	if(r1-l1<=r2-l2)//比较哪条边短
	{
		for(mid=l2+r2>>1,i=l1;i<=r1;++i) for(D.Dij(i,mid,l1,r1,l2,r2),//枚举中线上的点跑最短路
			j=l;j<=r;++j) Gmin(ans[q[j].p],D[P(q[j].x1,q[j].y1)]+D[P(q[j].x2,q[j].y2)]);//枚举询问更新答案
		for(i=l;i<=r;++i)//把询问分到两个小矩形中
			q[i].y1<=mid&&q[i].y2<=mid&&(sl[++tl]=q[i],0),//若两点都在第一个矩形
			q[i].y1>mid&&q[i].y2>mid&&(sr[++tr]=q[i],0);//若两点都在第二个矩形
		for(i=1;i<=tl;++i) q[l+i-1]=sl[i];for(i=1;i<=tr;++i) q[l+tl+i-1]=sr[i];//把询问存回原数组
		Solve(l1,r1,l2,mid,l,l+tl-1),Solve(l1,r1,mid+1,r2,l+tl,l+tl+tr-1);//递归
	}
	else//和上面相似,不重复解释了
	{
		for(mid=l1+r1>>1,i=l2;i<=r2;++i) for(D.Dij(mid,i,l1,r1,l2,r2),
			j=l;j<=r;++j) Gmin(ans[q[j].p],D[P(q[j].x1,q[j].y1)]+D[P(q[j].x2,q[j].y2)]);
		for(i=l;i<=r;++i)
			q[i].x1<=mid&&q[i].x2<=mid&&(sl[++tl]=q[i],0),
			q[i].x1>mid&&q[i].x2>mid&&(sr[++tr]=q[i],0);
		for(i=1;i<=tl;++i) q[l+i-1]=sl[i];for(i=1;i<=tr;++i) q[l+tl+i-1]=sr[i];
		Solve(l1,mid,l2,r2,l,l+tl-1),Solve(mid+1,r1,l2,r2,l+tl,l+tl+tr-1);
	}
}
int main()
{
	RI i,j;for(F.read(n,m),i=1;i<=n;w[P(i,m)][0]=w[P(i,m-1)][1],++i)
		for(j=1;j^m;++j) F.read(w[P(i,j)][1]),w[P(i,j)][0]=w[P(i,j-1)][1];
	for(i=1;i^n;++i) for(j=1;j<=m;++j) F.read(w[P(i,j)][3]),w[P(i,j)][2]=w[P(i-1,j)][3];
	for(j=1;j<=m;++j) w[P(n,j)][2]=w[P(n-1,j)][3];
	for(F.read(Qt),i=1;i<=Qt;++i) F.read(q[i].x1,q[i].y1,q[i].x2,q[i].y2),ans[q[i].p=i]=INF;
	for(Solve(1,n,1,m,1,Qt),i=1;i<=Qt;++i) F.writeln(ans[i]);return F.clear(),0;
}
posted @ 2020-06-01 12:09  TheLostWeak  阅读(145)  评论(0编辑  收藏  举报