【ZJOI2016】旅行者 - 题解
题意:
给你一个网格图以及图上的边权,多个询问,求网格图内两点的最短路径。点数不多于 $ 20000 $ ,询问不超过 $ 100000 $ ,边权不超过 $ 10000 $ 。
题解:
算法一:
对于每次询问暴力跑两点最短路,听说是网格图, $ Spfa $ 再见, $ Dijkstra $ 加堆优化飞起。得分 $ 20 $ 。
算法二:
在线做法已经很难再快了,考虑离线。这是一个网格图,所以我们考虑分治,每次处理左上端点为 $ (x1, y1) $ 右下端点为 $ (x2, y2) $ 矩形且询问的点都在范围内的询问。然后我们要将这个矩形切开,当然最好是从中间切开。然后我们考虑当前所有询问经过中线的答案是否比当前答案更优,然后将询问分类并进行分治(跨过中线的两个点答案已经算好,其他的还需递归分治)。我们发现将矩形较长边从中间切开较优。时间复杂度玄学(听说是 $ O(n \sqrt{n} \log{n}) $ 的)。
咦,被卡常了?得分 $ 50 $ ~ $ 100 $ 分。
算法三:
考虑剪枝。如果当前矩形内询问只有一两个时直接暴力查询即可,其实也没快多少。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=20010,M=100010,INF=2e9;
const int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
int n,m,k,e[N][4];
struct Q { int l1,r1,l2,r2,id; }; Q q[M],ta[M],tb[M];
int ans[M],dis[N];
struct D {
int id,w; D(int Id=0,int W=0):id(Id),w(W) {}
inline bool operator<(const D &yy)const { return w>yy.w; }
};priority_queue <D> h;
int read() {
register int tmp=0;register char c=getchar();
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';tmp=(tmp<<1)+(tmp<<3)+(c^48),c=getchar());
return tmp;
}
inline int Min(int x,int y) { return x<y? x:y; }
inline int g(int x,int y) { return (x-1)*m+y; }
void Dijkstra(int l1,int r1,int l2,int r2,int s) {
for(int i=l1;i<=l2;i++) for(int j=r1;j<=r2;j++) dis[g(i,j)]=INF;
dis[s]=0,h.push(D(s,0));
for(;!h.empty();) {
D u=h.top(); h.pop(); if(u.w!=dis[u.id]) continue;
int x=(u.id-1)/m+1,y=(u.id-1)%m+1,tx,ty,w;
for(int i=0;i<4;i++) {
tx=x+dir[i][0],ty=y+dir[i][1],w=e[u.id][i];
if(tx<l1||tx>l2||ty<r1||ty>r2) continue;
if(dis[g(tx,ty)]>u.w+w)
dis[g(tx,ty)]=u.w+w,h.push(D(g(tx,ty),dis[g(tx,ty)]));
}
}
}
void solve(int l1,int r1,int l2,int r2,int l,int r) {
if(l1>l2||r1>r2||l>r) return ;
if(l==r) {
Dijkstra(l1,r1,l2,r2,g(q[l].l1,q[l].r1));
ans[q[l].id]=Min(dis[g(q[l].l2,q[l].r2)],ans[q[l].id]); return ;
}
if(l2-l1<=r2-r1) {
int mid=(r1+r2)/2;
for(int i=l1;i<=l2;i++) {
Dijkstra(l1,r1,l2,r2,g(i,mid));
for(int j=l;j<=r;j++)
ans[q[j].id]=Min(ans[q[j].id],
dis[g(q[j].l1,q[j].r1)]+dis[g(q[j].l2,q[j].r2)]);
}
int la=0,lb=0;
for(int i=l;i<=r;i++) {
if(q[i].r1<=mid&&q[i].r2<=mid) ta[++la]=q[i];
if(q[i].r1>mid&&q[i].r2>mid) tb[++lb]=q[i];
}
for(int i=1;i<=la;i++) q[i+l-1]=ta[i];
for(int i=1;i<=lb;i++) q[r-i+1]=tb[i];
if(r1==r2) return ;
solve(l1,r1,l2,mid,l,l+la-1),solve(l1,mid+1,l2,r2,r-lb+1,r);
}
else {
int mid=(l1+l2)/2;
for(int i=r1;i<=r2;i++) {
Dijkstra(l1,r1,l2,r2,g(mid,i));
for(int j=l;j<=r;j++)
ans[q[j].id]=Min(ans[q[j].id],
dis[g(q[j].l1,q[j].r1)]+dis[g(q[j].l2,q[j].r2)]);
}
int la=0,lb=0;
for(int i=l;i<=r;i++) {
if(q[i].l1<=mid&&q[i].l2<=mid) ta[++la]=q[i];
if(q[i].l1>mid&&q[i].l2>mid) tb[++lb]=q[i];
}
for(int i=1;i<=la;i++) q[i+l-1]=ta[i];
for(int i=1;i<=lb;i++) q[r-i+1]=tb[i];
if(l1==l2) return ;
solve(l1,r1,mid,r2,l,l+la-1),solve(mid+1,r1,l2,r2,r-lb+1,r);
}
}
int main() {
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<m;j++) e[g(i,j)][1]=e[g(i,j+1)][3]=read();
for(int i=1;i<n;i++)
for(int j=1;j<=m;j++) e[g(i,j)][0]=e[g(i+1,j)][2]=read();
k=read();
for(int i=1;i<=k;i++) {
q[i].id=i,ans[i]=INF;
q[i].l1=read(),q[i].r1=read(),q[i].l2=read(),q[i].r2=read();
}
solve(1,1,n,m,1,k); for(int i=1;i<=k;i++) printf("%d\n",ans[i]);
return 0;
}