\qquad 暴力加边,跑 D i j s t r a Dijstra Dijstra
\qquad 加边的时候二分或直接把点存进一个 m a p map map,暴力跑 D i j s t r a Dijstra Dijstra
\qquad 出门左转线段树优化建图模板题
\qquad 首先这题需要发现2个很有用的性质:
性质 1 1 1,用 D i j s t r a Dijstra Dijstra跑最短路时,对于每个点,都只会在堆中弹出1次
性质 2 2 2,对于每个弹跳装置,通过这一个弹跳装置到达的矩形中所有城市需要的时间相同(不是矩形中每个城市的最短路相同,而是通过这个装置到达它们的时间相同)
\qquad 观察性质 2 2 2,我们发现可以把整个矩形放入 D i j s t r a Dijstra Dijstra中,对于每个堆顶弹出的矩形,我们只需要取其中任意一个之前没出现过的城市,都能保证这是该点的最短路径。那么,我们就把这题转换成了实现如下操作:
\qquad 至于为什么只取矩形中的一个点,因为本蒟蒻太菜了,感觉不好操作,也只是常数问题。
\qquad 那么这题就被我们转换成了一道支持插入、删除任意一点、在某特定范围中查找第一个点的的二维数据结构+ D i j s t r a Dijstra Dijstra的题目。
\qquad 因为我们需要找到第一个未出现过的城市的确切位置,那么明显需要二分,所以外面就选择线段树。里面的比较随意,支持删除、可以动态开空间就行。而这题卡空间,不能用二维线段树,于是我们可以选择线段树套平衡树,为了方便,直接上 s e t set set。
\qquad 因为小蒟蒻的写法很不优秀,可能要查询(m+n)次,线段树查询最多 l o g log log个区间,每个区间用set查询,时间复杂度 O ( ( m + n ) l o g 2 2 n ) O((m+n)log_2^2n) O((m+n)log22n)。插入城市时,深度为 l o g ( n ) log(n) log(n),往线段树插入n个城市,所以空间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
// luogu-judger-enable-o2
#include
using namespace std;
inline int read(){
int a=0;char c=getchar();
while(c>57 or c<48)c=getchar();
while(47<c and c<58){
a=a*10+c-48;
c=getchar();
}
return a;
}//快读
#define Ls (x<<1)
#define Rs (x<<1|1)
#define MN 70005
#define INF 1e9
struct P{
int id,x,y;
bool friend operator<(P a,P b){
if(a.y==b.y)return a.x<b.x;
return a.y<b.y;
//为什么用y作为第一关键字下面解释
}
P(int ID=0,int X=0,int Y=0){
id=ID;x=X;y=Y;
}
};//关于城市的结构体,记录城市编号和坐标
set<P>num[MN<<2];
int sum[MN<<2],f[MN],x[MN],y[MN],n,m,W,H,cnt;
//sum:线段树中某个区间的城市总数
//f:各个点的最短路
//cnt:松弛完成的的点的数量
struct Mat{
int t,l,r,d,u;
Mat(int T=0,int L=0,int R=0,int D=0,int U=0){
t=T;l=L;r=R;d=D;u=U;
}
bool friend operator<(Mat a,Mat b){
return a.t>b.t;
}
};//矩阵的结构体,记录时间和大小
vector<Mat>edge[MN];//记录每个城市的弹跳装置指向的矩形
priority_queue<Mat>Q;//优先队列
void Insert(int x,int l,int r,int loc,int id,int y){
//x坐标和线段树的编号重了,改成了loc
int mid=(l+r)>>1;
num[x].insert(P(id,loc,y));
sum[x]++;
if(l==r)return;
if(mid>=loc)Insert(Ls,l,mid,loc,id,y);
else Insert(Rs,mid+1,r,loc,id,y);
}//在线段树中插入城市
void del(int x,int l,int r,int loc,int id,int y){
int mid=(l+r)>>1;
num[x].erase(num[x].find(P(id,loc,y)));
sum[x]--;
if(l==r)return;
if(mid>=loc)del(Ls,l,mid,loc,id,y);
else del(Rs,mid+1,r,loc,id,y);
}//在线段树中删除城市
int ask(int x,int l,int r,int b,int e,int L,int R){
if(!sum[x]||l>e||r<b)return 0;
if(b<=l&&r<=e){
set<P>::iterator it=num[x].lower_bound(P(0,b,L));
//这里,因为我们的横坐标肯定符合要求,但是纵坐标可能不符合
//所以我们以纵坐标为第一关键字,这样就不会出现明明有合法的城市却找出了1个不合法的来
if(it==num[x].end()) return 0;//记得判断是否是在set中的,不然后面删除会RE
int tmp=(*num[x].lower_bound(P(0,b,L))).id;
if(y[tmp]>R||y[tmp]<L)return 0;//判断是否合法
return tmp;
}
int mid=(l+r)>>1;
int tmp=ask(Ls,l,mid,b,e,L,R);
if(tmp) return tmp;
return ask(Rs,mid+1,r,b,e,L,R);
}//找出区间中第一个点
void Dij(){
while(!Q.empty())Q.pop();
for(int i=1;i<=n;++i)f[i]=INF;
f[1]=0;
Q.push(Mat(0,x[1],x[1],y[1],y[1]));
while(!Q.empty()){
Mat w=Q.top();
int cur=ask(1,1,W,w.l,w.r,w.d,w.u);
if(!cur){Q.pop();continue;}//一个矩形里没有城市时再删除
del(1,1,W,x[cur],cur,y[cur]);
f[cur]=w.t;
cnt++;
if(cnt==n)break;//松弛完直接break,因为后面的查询也需要log^2的时间但是肯定不会对结果造成影响
for(int i=0;i<edge[cur].size();++i){
edge[cur][i].t+=f[cur];
Q.push(edge[cur][i]);
edge[cur][i].t-=f[cur];
}
}
}//Dijstra
int main(){
n=read();m=read();W=read();H=read();
for(int i=1;i<=n;++i){
x[i]=read();y[i]=read();
Insert(1,1,W,x[i],i,y[i]);
}//插入城市
Mat w;
int p;
for(int i=1;i<=m;++i){
p=read();w.t=read();w.l=read();w.r=read();w.d=read();w.u=read();
edge[p].push_back(w);
}
Dij();
for(int i=2;i<=n;++i)printf("%d\n",f[i]);
return 0;
}