P5812 [IOI2019] 天桥 题解
吐槽:我在网上能找到的所有题解,要么对关键性质的阐述过于简洁,让我这个低水平选手看得云里雾里;要么推广关键性质时的说明过少,或是给出了对想出正解毫无帮助的部分分性质,让题解有失严谨性与逻辑性。
题目想让我们找一条最短路径。虽然这张图是平面图,但最短路径不免错综复杂,所以我们先确定基本思路:建图跑 Dijkstra 最短路。
原图的点数可以达到
我们先考虑一种特殊情况:不存在一座天桥
在这种情况下,我们有一个关键性质:
性质 存在一条最优路径,满足:若我们自下而上地进入一座天桥,或自上而下地离开一座天桥,那么进入点(或离开点)一定是这座天桥的某个端点。
注 1 “进入” 表示我们之前不在这座天桥上,现在在这座天桥上;“离开” 表示我们之前在这座天桥上,现在不在这座天桥上。不在这座天桥上时,我们可能在某栋建筑上上下移动,也可能在另一座天桥上左右移动。
注 2 “自下而上地进入” 表示我们进入这座天桥之前,我们在某栋建筑上向上移动;“自上而下地离开” 表示我们离开这座天桥之后,我们在某栋建筑上向下移动。
注 3 进入点与离开点都一定在某栋建筑上。注 4 即使路径与这座天桥只有一个交点(公共点),我们也认为这条路径 “进入” 与 “离开” 了这座天桥各一次。
我们先证明其中一种情况。假设现在有一条最优路径,它自下而上地进入了一座天桥
有三种情况:进入天桥
- 向左移动了一段距离(图
1 ); - 向右移动了一段距离(图
2 ); - 没有左右移动(图
3 )。
以图
若路径
那么
若
那么
设
我们发现
对于图
UPD 这里我想复杂了。考虑天桥与它两端点所在的建筑构成了 “冂” 形,而起点没有被 “冂” 形包住,所以路径必然经过了 “冂” 形,只要讨论路径最后一次位于 “冂” 形上时在三条线段中的哪一条上即可。
两种证法本质相同,但这种显然更简洁。
同理,若天桥
到这里我们已经证明了,对于一座特定的天桥
由前文的证明可得,一次调整的过程一定形如下图:若对于天桥
上文中我们讨论的都是 “不存在一座天桥
若天桥
对于
有了这个关键性质,我们就可以对原图进行简化了。假设我们从天桥
这样点数和边数都被缩减到了
如果要问这个关键性质是怎么想到的,按我的理解,大概是对着
代码写得很丑,仅供参考。
#include <bits/stdc++.h>
#define eb emplace_back
#define ls u<<1
#define rs u<<1|1
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;
int n,m,M,S,G,t=1,L,R,x[100005],h[100005],p[100005];
int l[100005],r[100005],y[100005],z[100005];
int ti[400005],a[200015],b[200015],cnt,bg,ed,tot;
vector<pii> vec[100005],e[1200005];
set<int> s;
set<int>::iterator it;
map<int,int> mp[100005];
priority_queue<pli,vector<pli>,greater<pli> > q;
ll dis[1200005];
inline pii calc(int x,pii u,vector<pii> &V){
it=s.lower_bound(x),R=*it;
L=*(R^x?--it:it);
V.eb(pii(u.fi,L)),V.eb(pii(R,u.se));
u.fi=L,u.se=R;
return u;
}
void cover(int u,int l,int r,int x,int y,int z){
if(x<=l && r<=y)return ti[u]=z,void();
int mid=(l+r)>>1;
if(x<=mid)cover(ls,l,mid,x,y,z);
if(y>mid)cover(rs,mid+1,r,x,y,z);
}
int que(int u,int l,int r,int x){
if(l==r)return ti[u];
int mid=(l+r)>>1;
if(x<=mid)return max(ti[u],que(ls,l,mid,x));
return max(ti[u],que(rs,mid+1,r,x));
}
inline int id(int x,int y){
if(mp[y].count(x))return mp[y][x];
return mp[y][x]=++tot;
}
inline void ins(int u,int v,int w){
e[u].eb(pii(v,w));
e[v].eb(pii(u,w));
}
inline void work(int k){
cnt=0;
for(auto u:vec[k])
a[++cnt]=u.fi,a[++cnt]=u.se;
sort(a+1,a+1+cnt);
cnt=unique(a+1,a+1+cnt)-a-1;
for(int i=1,u,v;i<=cnt;++i){
u=id(a[i],k);
if((v=que(1,1,n,a[i]))==-1)continue;
ins(u,id(a[i],v),z[k]-z[v]);
}
for(auto u:vec[k])
cover(1,1,n,u.fi,u.se,k);
}
inline void work2(int k){
cnt=0,t=1;
for(auto u:mp[k])
a[++cnt]=u.fi,b[cnt]=u.se;
sort(vec[k].begin(),vec[k].end());
for(auto u:vec[k]){
while(t<=cnt && a[t]<u.fi)++t;
while(t<cnt && a[t+1]<=u.se)
ins(b[t],b[t+1],x[a[t+1]]-x[a[t]]),++t;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d%d",x+i,h+i);
s.insert(i),p[i]=i;
}
sort(p+1,p+1+n,[](int X,int Y){
return h[X]<h[Y];
});
for(int i=1;i<=m;++i){
scanf("%d%d%d",l+i,r+i,y+i);
++l[i],++r[i],z[i]=y[i];
}
sort(z+1,z+1+m);
M=unique(z+1,z+1+m)-z-1;
for(int i=1;i<=m;++i){
y[i]=lower_bound(z+1,z+1+M,y[i])-z;
vec[y[i]].eb(l[i],r[i]);
}
scanf("%d%d",&S,&G),++S,++G;
if(S>G)swap(S,G);
for(int i=1;i<=n*4;++i)ti[i]=-1;
bg=id(S,0),cover(1,1,n,S,S,0);
ed=id(G,0),cover(1,1,n,G,G,0);
for(int k=1;k<=M;++k){
while(t<=n && h[p[t]]<z[k])s.erase(p[t++]);
for(int i=0;i<(int)vec[k].size();++i){
pii u=vec[k][i];
if(u.fi<S && S<u.se)
vec[k][i]=calc(S,u,vec[k]);
else if(u.fi<G && G<u.se)
vec[k][i]=calc(G,u,vec[k]);
}
work(k);
}
for(int i=1;i<=M;++i)work2(i);
for(int i=1;i<=tot;++i)dis[i]=2e18;
dis[bg]=0,q.push(pli(0,bg));
while(!q.empty()){
pli t=q.top();q.pop();
int u=t.se;
if(dis[u]<t.fi)continue;
dis[u]=t.fi;
for(auto v:e[u]){
if(dis[v.fi]<=dis[u]+v.se)continue;
dis[v.fi]=dis[u]+v.se;
q.push(pli(dis[v.fi],v.fi));
}
}
printf("%lld\n",dis[ed]<1e18?dis[ed]:-1);
return 0;
}