bzoj 4456: [Zjoi2016]旅行者

分治+最短路
离线,分治,每次查询起点终点都在[x1,x2][y1,y2]内的答案。接下来讨论x2-x1>y2-y1的情况,反之类比即可。
现在我们要计算的是路径范围在这个矩形之内,且路径经过中轴线的答案。
枚举中轴线上的每个点,计算它到矩形内的点的最短路,然后用dis[a]+dis[b]更新询问的答案。
之后分治查询两个点在中轴线同一边的答案。
这样做为什么是对的?
在分治的过程中,如果两个点不同,那么他们一定会被其中一条中轴线分开而属于两块。
途径两个块的路径一定会被至少一条中轴线割开,否则他们就一直在一起了, 与假设矛盾。
而如果两个点相同,那么答案就是0了。
   
   
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
 
#define ll long long
#define inf 1e9
#define eps 1e-8
#define md
#define N 20010
#define M 100010
using namespace std;
struct QQ { int x,l;};
struct cmp
{
bool operator () (QQ a,QQ b) { return a.l>b.l;}
};
priority_queue<QQ,vector<QQ>,cmp> q;
struct PP { int x0,x1,y0,y1,id;} p[M],s[M];
struct yts { int x,t,l,ne;} e[4*N];
int X[N],Y[N],dis[N],v[N],ans[M];
bool vis[N];
int n,m,num;
inline int get(int x,int y) { return (x-1)*m+y;}
 
void put(int x,int y,int l)
{
num++; e[num].x=x; e[num].t=y; e[num].l=l;
e[num].ne=v[x]; v[x]=num;
}
 
void dijstra(int x,int nl,int nr,int ml,int mr)
{
for (int i=nl;i<=nr;i++)
for (int j=ml;j<=mr;j++)
{
int t=get(i,j);
dis[t]=inf,vis[t]=0;
}
dis[x]=0; q.push((QQ){x,0});
while (!q.empty())
{
int x=q.top().x; q.pop();
if (vis[x]) continue; vis[x]=1;
for (int i=v[x];i;i=e[i].ne)
{
int y=e[i].t;
if (X[y]<nl||X[y]>nr||Y[y]<ml||Y[y]>mr) continue;
if (dis[y]>dis[x]+e[i].l)
{
dis[y]=dis[x]+e[i].l;
q.push((QQ){y,dis[y]});
}
}
}
}
 
void solve(int nl,int nr,int ml,int mr,int ql,int qr)
{
if (ql>qr) return;
if (nr-nl>mr-ml)
{
int mid=(nl+nr)>>1;
for (int i=ml;i<=mr;i++)
{
dijstra(get(mid,i),nl,nr,ml,mr);
for (int j=ql;j<=qr;j++)
ans[p[j].id]=min(ans[p[j].id],dis[get(p[j].x0,p[j].y0)]+dis[get(p[j].x1,p[j].y1)]);
}
int l=ql-1,r=qr+1;
for (int i=ql;i<=qr;i++)
if (p[i].x0<mid&&p[i].x1<mid) s[++l]=p[i];
else if (p[i].x0>mid&&p[i].x1>mid) s[--r]=p[i];
for (int i=ql;i<=l;i++) p[i]=s[i];
for (int i=r;i<=qr;i++) p[i]=s[i];
solve(nl,mid-1,ml,mr,ql,l); solve(mid+1,nr,ml,mr,r,qr);
}
else
{
int mid=(ml+mr)>>1;
for (int i=nl;i<=nr;i++)
{
dijstra(get(i,mid),nl,nr,ml,mr);
for (int j=ql;j<=qr;j++)
ans[p[j].id]=min(ans[p[j].id],dis[get(p[j].x0,p[j].y0)]+dis[get(p[j].x1,p[j].y1)]);
}
int l=ql-1,r=qr+1;
for (int i=ql;i<=qr;i++)
if (p[i].y0<mid&&p[i].y1<mid) s[++l]=p[i];
else if (p[i].y0>mid&&p[i].y1>mid) s[--r]=p[i];
for (int i=ql;i<=l;i++) p[i]=s[i];
for (int i=r;i<=qr;i++) p[i]=s[i];
solve(nl,nr,ml,mid-1,ql,l); solve(nl,nr,mid+1,mr,r,qr);
}
}
 
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
int t=get(i,j);
X[t]=i; Y[t]=j;
}
for (int i=1;i<=n;i++)
for (int j=1;j<m;j++)
{
int l; scanf("%d",&l);
int x=get(i,j),y=get(i,j+1);
put(x,y,l); put(y,x,l);
}
for (int i=1;i<n;i++)
for (int j=1;j<=m;j++)
{
int l; scanf("%d",&l);
int x=get(i,j),y=get(i+1,j);
put(x,y,l); put(y,x,l);
}
int Q,w=0;
scanf("%d",&Q);
for (int i=1;i<=Q;i++)
{
w++;
scanf("%d%d%d%d",&p[w].x0,&p[w].y0,&p[w].x1,&p[w].y1);
if (p[w].x0==p[w].x1&&p[w].y0==p[w].y1) w--,ans[i]=0;
else p[w].id=i,ans[i]=inf;
}
solve(1,n,1,m,1,w);
for (int i=1;i<=Q;i++) printf("%d\n",ans[i]);
printf("\n");
return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值