[ZJOI2016]旅行者-分治+最短路
作者:互联网
一道牛逼题
题目大意
给定一张\(n*m\)的网格图\((n*m\le 2\times10^4)\),\(Q(Q\le2\times10^5)\)次查询,询问两点之间的距离。
题目解法
欢迎来我博客pmt2018查看
有老哥说看到网格图就想到分治,反正我是听了题解才会的。
做法是对每次对网格图的长边一切为二,考虑两种情况
两点不跨过中线,这时两点的最短路有两种情况,要么不跨过中线,我们这时枚举中线上的点用最短路将其更新,要么跨过中线,这时我们将其分治下去继续做。
两点跨过中线,这时分治下去也没啥用了。
所以我们把所有询问离线下来,像整体二分一样的处理就好了。
正确性显然,听说复杂度是\(O(S\sqrt S\ log_2S)\)的,但是我不会证明。
代码如下
#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define y0 pmt
#define y1 pmtpmt
#define x0 pmtQAQ
#define x1 pmtQwQ
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int > vi;
typedef pair<int ,int > pii;
const int INF=0x3f3f3f3f, maxn=100007, MOD=1e9+7;
const ll LINF=0x3f3f3f3f3f3f3f3fLL;
const ll P=19260817;
int n,m;
int Q;
struct Point{
int x,y;
Point(){}
Point(int _x,int _y){x=_x,y=_y;}
friend bool operator < (const Point &x,const Point &y){
if(x.x!=y.x)return x.x<y.x;
return x.y<y.y;
}
};
int id(Point x){return (x.x-1)*m+x.y;}
bool in(Point x,int lx,int rx,int ly,int ry){
return lx<=x.x&&x.x<=rx&&ly<=x.y&&x.y<=ry;
}
struct edge{
int nxt,w;
Point to;
}e[maxn<<2];
int head[maxn],tot;
void addedge(Point u,Point v,int w){
e[++tot].to=v;
e[tot].w=w;
e[tot].nxt=head[id(u)];
head[id(u)]=tot;
}
struct query{
Point x,y;
int id;
}q[maxn<<2],q1[maxn<<1],q2[maxn<<1];
int ans[maxn];
priority_queue<pair<int ,Point >,vector<pair<int ,Point > >,greater<pair<int ,Point > > > pq;
int dis[maxn];
int vis[maxn];
void dij(int lx,int rx,int ly,int ry,int x,int y){//最短路,处理(x,y)到(lx~rx,ly~ry)矩形里的最短路
for(int i=lx;i<=rx;i++)for(int j=ly;j<=ry;j++)dis[id(Point(i,j))]=INF,vis[id(Point(i,j))]=0;
dis[id(Point(x,y))]=0;
pq.push(mp(0,Point(x,y)));
while(!pq.empty()){
Point u=pq.top().se;int d=pq.top().fi;pq.pop();
if(d!=dis[id(u)]||vis[id(u)])continue;
vis[id(u)]=true;
for(int i=head[id(u)];i;i=e[i].nxt){
Point v=e[i].to;
if(in(v,lx,rx,ly,ry)&&dis[id(u)]+e[i].w<dis[id(v)]){
dis[id(v)]=dis[id(u)]+e[i].w;
pq.push(mp(dis[id(v)],v));
}
}
}
}
void solve(int lx,int rx,int ly,int ry,int l,int r){//(lx~rx,ly~ry)表示当前处理的矩形,x~y表示我们当前处理的询问的范围
if(lx==rx&&ly==ry){
for(int i=l;i<=r;i++)ans[q[i].id]=0;
return ;
}
if(l>r)return;
if(rx-lx>=ry-ly){//我们选择切矩形较长的一边
int mid=(lx+rx)>>1;
for(int i=ly;i<=ry;i++){//枚举中线上的点
dij(lx,rx,ly,ry,mid,i);
for(int j=l;j<=r;j++)ans[q[j].id]=min(ans[q[j].id],dis[id(q[j].x)]+dis[id(q[j].y)]);//更新答案
}
int top1=0,top2=0;
for(int i=l;i<=r;i++){
//将不跨过中线的点分治处理
if(in(q[i].x,lx,mid,ly,ry)&&in(q[i].y,lx,mid,ly,ry))q1[++top1]=q[i];
if(in(q[i].x,mid+1,rx,ly,ry)&&in(q[i].y,mid+1,rx,ly,ry))q2[++top2]=q[i];
}
for(int i=l;i<=l+top1-1;i++)q[i]=q1[i-l+1];
for(int i=l+top1;i<=l+top1+top2-1;i++)q[i]=q2[i-l-top1+1];
//分治切开的两个矩形
solve(lx,mid,ly,ry,l,l+top1-1);solve(mid+1,rx,ly,ry,l+top1,l+top1+top2-1);
}else{
//与上面同理
int mid=(ly+ry)>>1;
for(int i=lx;i<=rx;i++){
dij(lx,rx,ly,ry,i,mid);
for(int j=l;j<=r;j++)ans[q[j].id]=min(ans[q[j].id],dis[id(q[j].x)]+dis[id(q[j].y)]);
}
int top1=0,top2=0;
for(int i=l;i<=r;i++){
if(in(q[i].x,lx,rx,ly,mid)&&in(q[i].y,lx,rx,ly,mid))q1[++top1]=q[i];
if(in(q[i].x,lx,rx,mid+1,ry)&&in(q[i].y,lx,rx,mid+1,ry))q2[++top2]=q[i];
}
for(int i=l;i<=l+top1-1;i++)q[i]=q1[i-l+1];
for(int i=l+top1;i<=l+top1+top2-1;i++)q[i]=q2[i-l-top1+1];
solve(lx,rx,ly,mid,l,l+top1-1);solve(lx,rx,mid+1,ry,l+top1,l+top1+top2-1);
}
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<m;j++){
int x;scanf("%d",&x);
addedge(Point(i,j),Point(i,j+1),x);
addedge(Point(i,j+1),Point(i,j),x);
}
}
for(int i=1;i<n;i++){
for(int j=1;j<=m;j++){
int x;scanf("%d",&x);
addedge(Point(i,j),Point(i+1,j),x);
addedge(Point(i+1,j),Point(i,j),x);
}
}
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
Point x,y;
scanf("%d%d%d%d",&x.x,&x.y,&y.x,&y.y);
q[i].x=x,q[i].y=y,q[i].id=i;
}
memset(ans,0x3f,sizeof(ans));
solve(1,n,1,m,1,Q);
for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);
return 0;
}
标签:const,Point,int,短路,旅行者,ZJOI2016,lx,define 来源: https://www.cnblogs.com/pmt2018/p/11644666.html