【洛谷P3350】旅行者
题目
题目链接:https://www.luogu.com.cn/problem/P3350
小 Y 来到了一个新的城市旅行。她发现了这个城市的布局是网格状的,也就是有 \(n\) 条从东到西的道路和 \(m\) 条从南到北的道路,这些道路两两相交形成 \(n\times m\) 个路口 \((i,j)\), \((1\leq i\leq n,1\leq j\leq m)\)
她发现不同的道路路况不同,所以通过不同的路口需要不同的时间。通过调查发现,从路口 \((i,j)\) 到路口 \((i,j+1)\) 需要时间 \(r(i,j)\) ,从路口 \((i,j)\) 到路口 \((i+1,j)\) 需要时间 \(c(i,j)\) 。注意这里的道路是双向的。小 Y 有 \(q\) 个询问,她想知道从路口 \((x1,y1)\) 到路口 \((x2,y2)\) 最少需要花多少时间。
\(n\times m\leq 2\times 10^4\),\(q\leq 10^5\)。
思路
网格图上分治的套路。
对于一个 \(n\times m\) 的矩形,假设 \(n\geq m\)。考虑第 \(mid=\frac{1+n}{2}\) 行的贡献。
从这一行的 \(m\) 个点开始分别跑最短路,枚举所有属于该子矩形的询问更新答案。如果询问的两个点都在 \([1,mid-1]\) 行就往上继续分治;如果都在 \([mid+1,n]\) 行就往下分治,否则就不用继续分下去了。
复杂度不会证。看题解说是 \(O(nm\sqrt{nm}\log (nm))\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010,Inf=1e9;
int n,m,Q,ans[N];
vector<int> r[N],c[N],dis[N],vis[N];
struct node
{
int x1,y1,x2,y2,id;
}a[N],b[N];
struct node1
{
int dis,x,y;
friend bool operator <(node1 x,node1 y)
{
return x.dis>y.dis;
}
};
void dij(int sx,int sy,int lx,int rx,int ly,int ry)
{
for (int i=lx;i<=rx;i++)
for (int j=ly;j<=ry;j++)
dis[i][j]=Inf,vis[i][j]=0;
priority_queue<node1> q;
q.push((node1){0,sx,sy}); dis[sx][sy]=0;
while (q.size())
{
int x=q.top().x,y=q.top().y; q.pop();
if (vis[x][y]) continue;
vis[x][y]=1;
if (x<rx && dis[x+1][y]>dis[x][y]+c[x][y])
dis[x+1][y]=dis[x][y]+c[x][y],q.push((node1){dis[x+1][y],x+1,y});
if (x>lx && dis[x-1][y]>dis[x][y]+c[x-1][y])
dis[x-1][y]=dis[x][y]+c[x-1][y],q.push((node1){dis[x-1][y],x-1,y});
if (y<ry && dis[x][y+1]>dis[x][y]+r[x][y])
dis[x][y+1]=dis[x][y]+r[x][y],q.push((node1){dis[x][y+1],x,y+1});
if (y>ly && dis[x][y-1]>dis[x][y]+r[x][y-1])
dis[x][y-1]=dis[x][y]+r[x][y-1],q.push((node1){dis[x][y-1],x,y-1});
}
}
void solve(int lx,int rx,int ly,int ry,int ql,int qr)
{
if (ql>qr || lx>rx || ly>ry || (lx==rx && ly==ry)) return;
if (rx-lx>=ry-ly)
{
int mid=(lx+rx)>>1,pl=ql-1,pr=qr+1;
for (int i=ly;i<=ry;i++)
{
dij(mid,i,lx,rx,ly,ry);
for (int j=ql;j<=qr;j++)
ans[a[j].id]=min(ans[a[j].id],dis[a[j].x1][a[j].y1]+dis[a[j].x2][a[j].y2]);
}
for (int i=ql;i<=qr;i++)
{
if (max(a[i].x1,a[i].x2)<mid) b[++pl]=a[i];
if (min(a[i].x1,a[i].x2)>mid) b[--pr]=a[i];
}
for (int i=ql;i<=qr;i++) a[i]=b[i];
solve(lx,mid-1,ly,ry,ql,pl);
solve(mid+1,rx,ly,ry,pr,qr);
}
else
{
int mid=(ly+ry)>>1,pl=ql-1,pr=qr+1;
for (int i=lx;i<=rx;i++)
{
dij(i,mid,lx,rx,ly,ry);
for (int j=ql;j<=qr;j++)
ans[a[j].id]=min(ans[a[j].id],dis[a[j].x1][a[j].y1]+dis[a[j].x2][a[j].y2]);
}
for (int i=ql;i<=qr;i++)
{
if (max(a[i].y1,a[i].y2)<mid) b[++pl]=a[i];
if (min(a[i].y1,a[i].y2)>mid) b[--pr]=a[i];
}
for (int i=ql;i<=qr;i++) a[i]=b[i];
solve(lx,rx,ly,mid-1,ql,pl);
solve(lx,rx,mid+1,ry,pr,qr);
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
r[i].push_back(0); c[i].push_back(0);
for (int j=0;j<=m;j++)
dis[i].push_back(0),vis[i].push_back(0);
}
for (int i=1;i<=n;i++)
for (int j=1,x;j<m;j++)
scanf("%d",&x),r[i].push_back(x);
for (int i=1;i<n;i++)
for (int j=1,x;j<=m;j++)
scanf("%d",&x),c[i].push_back(x);
scanf("%d",&Q);
for (int i=1;i<=Q;i++)
{
scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
a[i].id=i; ans[i]=Inf;
if (a[i].x1==a[i].x2 && a[i].y1==a[i].y2) ans[i]=0;
}
solve(1,n,1,m,1,Q);
for (int i=1;i<=Q;i++)
cout<<ans[i]<<"\n";
return 0;
}