P3350 [ZJOI2016] 旅行者
咕了2天才写的题解
还是比较经典的题目,分治处理网格图最短路
离线下来,利用分治的思想,用一条线把网格图平均劈成两半,每次只考虑询问在两块的一对点,所有的线必须经过直线上的一个点,于是我把线上所有点都在规定范围内跑一次dijkstra,最后直接算答案,显然我想让最短路跑的次数最小,每次选较短的边作为线段的长度,从较高的边上劈开,如果一组询问在同一块,分别递归,就是子问题了。这个做法正确性显然。
时间复杂度证明如下:
设点数有
\(|V|\)
个,当前分治在第
\(k\)
轮,那么线的长度就最多是
\(\sqrt{\frac{|V|}{2^k}}\)
,要进行线的长度次的dijkstra,因为网格图点数与边数同级,所以每次dijkstra时间复杂度是
\(O(\frac{|V|}{2^k}\log\frac{|V|}{2^k})\)
,即
\(O(\frac{|V|}{2^k}\log{|V|})\)
,然后第
\(k\)
轮,要分治
\(2^k\)
次,时间复杂度是
\(O(2^k\cdot\sqrt{\frac{|V|}{2^k}}\frac{|V|}{2^k}\log{|V|})\)
,整理得
\(\frac{|V|^{\frac{3}{2}}\cdot \log{|V|}}{2^{\frac{k}{2}}}\)
,有
\(k\)
轮,分母相加,由等比数列求和公式,知道是个常数,如果我没算错的话就是
\(\sqrt{2}+1\)
,然后时间复杂度就是
\(O(|V|^{\frac{3}{2}}\cdot \log{|V|})\)
,这道题
\(|V|\)
最大是2e4,这么算下来最多也只有4e7级别
#include<bits/stdc++.h>
#define vd void
#define MAXN 200005
#define pr std::pair<int,int>
#define fi first
#define se second
const int inf=1e9;
int gi(){
char c;int x=0,f=0;
while(!isdigit(c=getchar()))f|=(c=='-');
while(isdigit(c))x=(x*10)+(c^48),c=getchar();
return f?-x:x;
}
template<class T>vd cnk(T&a,T b){a=a<=b?a:b;}
struct Q{
int pos,st,ed;
}query[MAXN],g[MAXN],g2[MAXN];
int n,m,q,ans[MAXN],x[MAXN],y[MAXN],dis[MAXN];
bool vis[MAXN];
std::vector<pr>nbr[MAXN];
struct node{
int to,W;
bool friend operator<(node a,node b){return a.W>b.W;}
};
std::priority_queue<node>pq;
int id(int xx,int yy){return (xx-1)*m+yy;}
vd add(int xx,int yy,int w){nbr[xx].emplace_back(yy,w),nbr[yy].emplace_back(xx,w);}
vd dijkstra(int s,int xl,int xr,int yl,int yr){
for(int i=xl;i<=xr;i++)for(int j=yl;j<=yr;j++)dis[id(i,j)]=inf,vis[id(i,j)]=0;
dis[s]=0;pq.push({s,0});
while(!pq.empty()){
int u=pq.top().to;pq.pop();
if(vis[u])continue;
vis[u]=1;
for(pr v:nbr[u]){
if(x[v.fi]<xl||x[v.fi]>xr||y[v.fi]<yl||y[v.fi]>yr)continue;
if(dis[v.fi]>dis[u]+v.se)dis[v.fi]=dis[u]+v.se,pq.push({v.fi,dis[v.fi]});
}
}
}
vd solve(int ql,int qr,int xl,int xr,int yl,int yr){
if(ql>qr||xl>xr||yl>yr)return;
if(xr-xl>=yr-yl){
int mid=(xl+xr)>>1,lg=0,lg2=0;
for(int i=yl;i<=yr;i++){
dijkstra(id(mid,i),xl,xr,yl,yr);
for(int j=ql;j<=qr;j++)cnk(ans[query[j].pos],dis[query[j].st]+dis[query[j].ed]);
}
for(int i=ql;i<=qr;i++){
if(x[query[i].st]<mid&&x[query[i].ed]<mid)g[++lg]=query[i];
else if(x[query[i].st]>mid&&x[query[i].ed]>mid)g2[++lg2]=query[i];
}
for(int i=1;i<=lg;i++)query[ql+i-1]=g[i];
for(int i=1;i<=lg2;i++)query[ql+lg+i-1]=g2[i];
solve(ql,ql+lg-1,xl,mid-1,yl,yr),solve(ql+lg,ql+lg+lg2-1,mid+1,xr,yl,yr);
}else{
int mid=(yl+yr)>>1,lg=0,lg2=0;
for(int i=xl;i<=xr;i++){
dijkstra(id(i,mid),xl,xr,yl,yr);
for(int j=ql;j<=qr;j++)cnk(ans[query[j].pos],dis[query[j].st]+dis[query[j].ed]);
}
for(int i=ql;i<=qr;i++){
if(y[query[i].st]<mid&&y[query[i].ed]<mid)g[++lg]=query[i];
else if(y[query[i].st]>mid&&y[query[i].ed]>mid)g2[++lg2]=query[i];
}
for(int i=1;i<=lg;i++)query[ql+i-1]=g[i];
for(int i=1;i<=lg2;i++)query[ql+lg+i-1]=g2[i];
solve(ql,ql+lg-1,xl,xr,yl,mid-1),solve(ql+lg,ql+lg+lg2-1,xl,xr,mid+1,yr);
}
}
int main(){
n=gi(),m=gi();
for(int i=1;i<=n;i++)for(int j=1;j<m;j++){int w=gi();add(id(i,j),id(i,j+1),w);};
for(int i=1;i<n;i++)for(int j=1;j<=m;j++){int w=gi();add(id(i,j),id(i+1,j),w);};
q=gi();
for(int i=1;i<=q;i++){
int a=gi(),b=gi(),c=gi(),d=gi();
query[i]={i,id(a,b),id(c,d)},ans[i]=inf;
}
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)x[id(i,j)]=i,y[id(i,j)]=j;
solve(1,q,1,n,1,m);
for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
return 0;
}
算是一个套路吧,网格图最短路想分治