【BZOJ】4456: [Zjoi2016]旅行者-最短路&二分

传送门:bzoj4456


题解

感觉自己对二分和分块还是不太熟悉啊。
此题可以离线,然后二分区域(长的边一分为二),每次让划分的两个区域边界上的每个点跑一遍当前整个区域的最短路,来更新还在当前区域内的询问的距离最小值。显然可以发现,对于起点终点都在同一边的询问,我们在接下来的二分当中还有可能更新到答案,其他的就不再下传。
此题貌似卡常,要用dijkstra+堆优化…
复杂度分析看这里(好复杂的样子,总之O(能过))


代码

#include<cstdio>
#include<queue>
#include<cctype>
#include<algorithm>
#define rc(x,y) ((x-1)*m+y)
#define X(x) ((x-1)/m+1)
#define Y(x) ((x-1)%m+1)
using namespace std;
typedef long long ll;
const int N=1e5+50,M=5e6+10;
const int inf=2e9;int dis[N];
int n,m,a[N],cnt,Q[N],id[N],sum;
int head[N],to[M],nxt[M],w[M],tp[N],tot;

struct qr{
   int st,ed,ans;
   qr(){ans=inf;}
}q[N];
inline int rd()
{
    char ch=getchar();int x=0,f=1;
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}

inline void lk(int u,int v,int c)
{
   to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=c;
}

inline bool in(int x,int xa,int ya,int xb,int yb)
{return (X(x)>=xa && X(x)<=xb && Y(x)>=ya && Y(x)<=yb);}

inline void gd(int x)
{
    int y=x<<1,z=Q[x],t=dis[Q[x]];
    if(y<cnt && dis[Q[y|1]]<dis[Q[y]]) y|=1;
    while(y<=cnt && dis[Q[y]]<t){
        Q[x]=Q[y];id[Q[x]]=x;
        x=y;y=x<<1;
        if(y<cnt && dis[Q[y|1]]<dis[Q[y]]) y|=1;
    }
    Q[x]=z;id[z]=x;
}

inline void gu(int x)
{
    int z=Q[x],t=dis[Q[x]];
    while(x>1&& dis[Q[x>>1]]>t){Q[x]=Q[x>>1];id[Q[x]]=x;x>>=1;}
    Q[x]=z;id[z]=x;
}

inline void dij(int now,int xa,int ya,int xb,int yb)
{
    int i,j,x;cnt=1;Q[1]=now;id[now]=1;dis[now]=0;
    for(i=xa;i<=xb;++i)
     for(j=ya;j<=yb;++j) if(rc(i,j)!=now){
        x=rc(i,j);dis[x]=inf;Q[++cnt]=x;id[x]=cnt;
      }
    while(cnt){
        x=Q[1];Q[1]=Q[cnt--];id[Q[1]]=1;gd(1);
        for(i=head[x];i;i=nxt[i]){
            j=to[i];
            if(in(j,xa,ya,xb,yb) && dis[j]>dis[x]+w[i]){
                dis[j]=dis[x]+w[i];gu(id[j]);
            }   
        }
    }
}

inline void div(int xa,int ya,int xb,int yb,int L,int R)
{
    int mid,i,j,k,l,r;
    if(L>R) return;
    if(yb-ya>xb-xa){
        mid=(ya+yb)>>1;
        for(i=xa;i<=xb;++i) {
          dij(rc(i,mid),xa,ya,xb,yb);   
          for(j=L;j<=R;++j){
            k=a[j];
            q[k].ans=min(q[k].ans,dis[q[k].st]+dis[q[k].ed]);
          }
        }
        l=L-1;r=R+1;
        for(i=L;i<=R;++i){
            k=a[i];
            if(Y(q[k].st)<mid && Y(q[k].ed)<mid) tp[++l]=k;
            else if(Y(q[k].st)>mid && Y(q[k].ed)>mid) tp[--r]=k;
        }
        for(i=L;i<=l;++i) a[i]=tp[i];for(i=r;i<=R;++i) a[i]=tp[i];
        div(xa,ya,xb,mid-1,L,l);div(xa,mid+1,xb,yb,r,R);
    }else{
        mid=(xa+xb)>>1;
        for(i=ya;i<=yb;++i){
            dij(rc(mid,i),xa,ya,xb,yb);
            for(j=L;j<=R;++j){
                k=a[j];
                q[k].ans=min(q[k].ans,dis[q[k].st]+dis[q[k].ed]);
            }
        }
        l=L-1;r=R+1;
        for(i=L;i<=R;++i){
            k=a[i];
            if(X(q[k].st)<mid && X(q[k].ed)<mid) tp[++l]=k;
            else if(X(q[k].st)>mid && X(q[k].ed)>mid) tp[--r]=k;
        }
        for(i=L;i<=l;++i) a[i]=tp[i];for(i=r;i<=R;++i) a[i]=tp[i];
        div(xa,ya,mid-1,yb,L,l);div(mid+1,ya,xb,yb,r,R);
    }
}

int main(){
   int i,j,zz,ix,iy;
   n=rd();m=rd();
   for(i=1;i<=n;++i)
    for(j=1;j<m;++j)
    {zz=rd();lk(rc(i,j),rc(i,j+1),zz);lk(rc(i,j+1),rc(i,j),zz);}
   for(i=1;i<n;++i)
    for(j=1;j<=m;++j)
    {zz=rd();lk(rc(i,j),rc(i+1,j),zz);lk(rc(i+1,j),rc(i,j),zz);}
   sum=rd();
   for(i=1;i<=sum;++i){a[i]=i;ix=rd();iy=rd();q[i].st=rc(ix,iy);ix=rd();iy=rd();q[i].ed=rc(ix,iy);}
   div(1,1,n,m,1,sum);
   for(i=1;i<=sum;++i) printf("%d\n",q[i].ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值