[NOI2019] 彈跳
阿新 • • 發佈:2022-03-31
desciption
有\(n\)個城市,所有城市在\(w*h\)的網格圖上,不同城市座標不同。
有\(m\)個彈跳機,\(i\)號彈跳機可以從城市\(p_i\)到\(l_i\le x \le r_i\),\(d_i\le y \le u_i\)的城市
問從城市1出發到所有點的最短路。
solution
kd-tree優化建圖
先用城市點建kd-tree
每個kd-tree上的點管轄範圍都是一個矩形
對於每個點,列舉以它為出發點的彈跳機,並在kd-tree上查詢該彈跳機能到達的矩形的子矩形連邊。
具體:
(kd-tree上節點\(n+1~2n\))
1.判斷彈跳機到達的矩形是否包含該節點的矩形(是就向該節點連邊return)
2.判斷該節點本身的城市是否在彈跳機矩形內(是就連邊)。
3.到達左右兩個節點(分別判斷前提:是否存在交集)
不過這樣做空間會爆
實際上並不需要連實邊。
你Dijskra()到點\(u\).直接再在kd-tree上臨時找邊即可。
具體:
1.\(u>n\):鬆弛本身城市節點和左右兒子
2.\(u<=n\):列舉以\(u\)出發的彈跳機,在kd-tree上找邊(類上面,不說了)。
code
點選檢視程式碼
#include<bits/stdc++.h> using namespace std; const int N=2e5+5; int n,m,W,H,nxt[N],to[N],head[N],ecnt,idx; int dis[N]; struct city { int z[2],id; bool operator<(const city &u) const{return z[idx]<u.z[idx];} }a[N]; char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf; #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) x=x*10+(ch^48),ch=getchar(); return x*f; } struct jump { int p,t,l[2],r[2]; }J[N]; void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;} struct node {int mid,l[2],r[2];}T[N]; int ls[N],rs[N]; struct pq { int p,w; bool operator<(const pq &u) const{return w>u.w;} }; priority_queue<pq> Q; void Update(int y,int z) { if(dis[y]>z) {Q.push((pq){y,dis[y]=z});} } struct kd_tree { int nd; void Build(int x,int l,int r) { int _idx(idx);idx^=1;int dx(idx); int mid=(l+r)>>1; nth_element(a+l,a+mid,a+r+1); T[x].mid=mid; T[x].l[0]=T[x].r[0]=a[mid].z[0];T[x].l[1]=T[x].r[1]=a[mid].z[1]; if(l!=mid) { ls[x]=++nd; Build(nd,l,mid-1);idx=dx;int L=ls[x]; T[x].l[0]=min(T[x].l[0],T[L].l[0]);T[x].l[1]=min(T[x].l[1],T[L].l[1]); T[x].r[0]=max(T[x].r[0],T[L].r[0]);T[x].r[1]=max(T[x].r[1],T[L].r[1]); } if(mid!=r) { rs[x]=++nd; Build(nd,mid+1,r);int R=rs[x]; T[x].l[0]=min(T[x].l[0],T[R].l[0]);T[x].l[1]=min(T[x].l[1],T[R].l[1]); T[x].r[0]=max(T[x].r[0],T[R].r[0]);T[x].r[1]=max(T[x].r[1],T[R].r[1]); } } void Mk_tree() { nd=n+1; T[nd].l[0]=1e9,T[nd].r[0]=0;T[nd].l[1]=1e9;T[nd].r[1]=0; for(int i=1;i<=n;i++) { T[nd].l[0]=min(T[nd].l[0],a[i].z[0]);T[nd].r[0]=max(T[nd].r[0],a[i].z[0]); T[nd].l[1]=min(T[nd].l[1],a[i].z[1]);T[nd].r[1]=max(T[nd].r[1],a[i].z[1]); } Build(nd,1,n); } void Go(int x,int id,int w) { //id(jump) idx^=1;int dx(idx); if(dis[x]<=w) return; if(T[x].l[0]>=J[id].l[0]&&T[x].l[1]>=J[id].l[1]&&T[x].r[0]<=J[id].r[0]&&T[x].r[1]<=J[id].r[1]) {Update(x,w);return;} int mid=T[x].mid,L(ls[x]),R(rs[x]),pos(a[mid].id); if(J[id].l[0]<=a[mid].z[0]&&J[id].l[1]<=a[mid].z[1]&&J[id].r[0]>=a[mid].z[0]&&J[id].r[1]>=a[mid].z[1]) {Update(pos,w);} if(L&&J[id].l[dx]<=T[L].r[dx])Go(L,id,w),idx=dx; if(R&&J[id].r[dx]>=T[R].l[dx])Go(R,id,w); } }KD; void DJ(int s) { memset(dis,0x3f,sizeof(dis)); Q.push((pq){s,0});dis[s]=0; while(!Q.empty()) { if(dis[Q.top().p]<Q.top().w){Q.pop();continue;} //There often be many same node in it(pop just leave the smallest one) int u=Q.top().p;Q.pop(); if(u>n) { if(ls[u]) {Update(ls[u],dis[u]);} if(rs[u]) {Update(rs[u],dis[u]);} Update(a[T[u].mid].id,dis[u]); } else { for(int i=head[u];i;i=nxt[i]) { int v=to[i]; idx=0;KD.Go(n+1,v,dis[u]+J[v].t); } } } } int main() { // freopen("data.in","r",stdin); // freopen("ans2.out","w",stdout); // scanf("%d%d%d%d",&n,&m,&W,&H); n=read(),m=read(),W=read(),H=read(); // printf("!%d\n",n); for(int i=1;i<=n;i++) {a[i].z[0]=read();a[i].z[1]=read();a[i].id=i;} KD.Mk_tree(); for(int i=1;i<=m;i++) { J[i].p=read();J[i].t=read();J[i].l[0]=read();J[i].r[0]=read();J[i].l[1]=read();J[i].r[1]=read(); // scanf("%d%d%d%d%d%d",&J[i].p,&J[i].t,&J[i].l[0],&J[i].r[0],&J[i].l[1],&J[i].r[1]); add_edge(J[i].p,i); } DJ(1); for(int i=2;i<=n;i++) printf("%d\n",dis[i]); return 0; }
qaq
- 還是卡常數luogu剛好卡過。可nkoj當然過不了啦。
因此我改了兩節課,試圖看我跟題解寫的有什麼區別(實際題解跑得也不是很快)
後來從討論版學到了:在kd_tree上\(u\)點時,判斷包含關係前,如果\(dis[u]\)已經小於我們要鬆弛的長度了。\(u\)和\(u\)的子樹都不用去了(因為kd-tree上時間邊權為\(0\),能到u的點都能以相同的\(dis\)到\(u\)的子樹,而能到\(u\)的子樹的不一定能到\(u\),所以\(dis[u子樹裡面的節點]<=dis[u]\))
於是加了這個剪枝我從倒數剛好卡過的解變成了rk2
太離譜了,可能大部分人都沒有看帖子吧。 - 還有一個我常錯的問題,注意遞迴函式全域性變數的回溯。