[ZJOI2016]旅行者-分治+最短路

一道牛逼题

题目大意

给定一张\(n*m\)的网格图\((n*m\le 2\times10^4)\),\(Q(Q\le2\times10^5)\)次查询,询问两点之间的距离。

传送门1

传送门2

传送门3

传送门4

题目解法

欢迎来我博客pmt2018查看

有老哥说看到网格图就想到分治,反正我是听了题解才会的。

做法是对每次对网格图的长边一切为二,考虑两种情况

  1. 两点不跨过中线,这时两点的最短路有两种情况,要么不跨过中线,我们这时枚举中线上的点用最短路将其更新,要么跨过中线,这时我们将其分治下去继续做。

  2. 两点跨过中线,这时分治下去也没啥用了。

所以我们把所有询问离线下来,像整体二分一样的处理就好了。

正确性显然,听说复杂度是\(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;
}

猜你喜欢

转载自www.cnblogs.com/pmt2018/p/11644666.html