1. 程式人生 > 其它 >[NOI2019] 彈跳

[NOI2019] 彈跳

傳送門P5471

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
    太離譜了,可能大部分人都沒有看帖子吧。
  • 還有一個我常錯的問題,注意遞迴函式全域性變數的回溯。