P5471 [NOI2019] 弹跳 / 线段树优化建图 + 无需建边
题目传送门:P5471 [NOI2019] 弹跳。
这咋这么牛 /qiang。
题目可以转化为若干个形如一个点连向一个矩形的边,然后跑最短路。
显然可以使用二维线段树优化建图,\(O(n\log^2 n)\),但是空间开不下 /ll。
由于这个图的建边非常特殊,考虑不建出边,相当于从 \(1\) 城市开始利用弹跳器走到一个矩形,然后再在矩形中找到可以到达的城市继续增广下一个矩形,那么我们直接将矩形建一个虚点然后直接 dij 即可,利用线段树维护横轴,set 维护纵轴,然后枚举过的点直接在 set 中删除,就能保证复杂度了。
由于是 set 所以空间就降低了很多。
#include<bits/stdc++.h>
#define pii pair<int,int>
#define lc k<<1
#define rc k<<1|1
#define double long double
using namespace std;
const int N=1.5e5+10;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
struct node{
int u,w,l1,r1,l2,r2,id;
}b[N];
priority_queue<pii,vector<pii>,greater<pii>>q;
vector<node>a[N];set<pii>t[N<<2];
int n,m,w,h,anss[N],vis[N],d[N];
void change(int k,int l,int r,int x,int p,int id){
t[k].insert({p,id});
if (l==r) return ;
int mid=l+r>>1;
if (x<=mid) change(lc,l,mid,x,p,id);
else change(rc,mid+1,r,x,p,id);
}
void solve(int k,int l,int r,int id){
if (b[id].l1<=l&&r<=b[id].r1){
set<pii>::iterator it=t[k].lower_bound({b[id].l2,0});
while(it!=t[k].end()&&b[id].r2>=(*it).first){
for (auto i:a[(*it).second]){
if (vis[i.id]) continue;
if (d[i.id]>d[id]+i.w){
d[i.id]=d[id]+i.w;
q.push({d[i.id],i.id});
}
}
anss[(*it).second]=min(anss[(*it).second],d[id]);
set<pii>::iterator tmp=it;
it++;
t[k].erase(tmp);
}
return ;
}
int mid=l+r>>1;
if (b[id].l1<=mid) solve(lc,l,mid,id);
if (b[id].r1>mid) solve(rc,mid+1,r,id);
}
main(){
n=read(),m=read(),w=read(),h=read();
for (int i=1;i<=n;i++){
int x=read(),y=read();
change(1,1,w,x,y,i);
}
for (int i=1;i<=m;i++) b[i]={read(),read(),read(),read(),read(),read(),i},a[b[i].u].push_back(b[i]);
memset(d,0x3f,sizeof(d)),memset(anss,0x3f,sizeof(anss));
for (auto i:a[1]) d[i.id]=i.w,q.push({i.w,i.id});
while(!q.empty()){
int u=q.top().second;q.pop();
vis[u]=1;
solve(1,1,w,u);
}
for (int i=2;i<=n;i++) printf("%d\n",anss[i]);
return 0;
}

浙公网安备 33010602011771号