传送门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
    太离谱了,可能大部分人都没有看帖子吧。
  • 还有一个我常错的问题,注意递归函数全局变量的回溯。