luogu Journeys (P6348)
\(\textsf{P6348 Journeys}\)
有 \(n\) 个点,\(m\) 次连边。每次连边给定 \(a,b,c,d\),将 \([a,b]\) 内所有的点向 \([c,d]\) 内所有的点连边,边权为 \(1\)。
求主城市 \(P\) 到每一点的最短路径长度,保证图连通。
\(m \le 10^5, n \le 5 \times 10^5\)。
因为暴力维护每条边,复杂度为 \(O(n^2 m)\),空间也会炸。
所以这题使用线段树优化建图。
线段树优化建图
维护两棵线段树。分别占用 \([1,4n]\) 与 \([4n+1,8n]\) 两个区间。
- 一棵从父节点向子节点连边权为 \(0\) 的边(\(\texttt{p}\) 向 \(\texttt{ls}\) 与 \(\texttt{rs}\) 分别连边)
- 一棵从子节点向父节点连边权为 \(0\) 的边(\(\texttt{ls+4n}\) 与 \(\texttt{rs+4n}\) 向 \(\texttt{p+4n}\) 连边)
叶子节点分别对应图上的每个点。所以对于每一个叶子节点 \(\texttt{p}\),都要与 \(\texttt{p+4n}\) 连边,边权为 \(0\)。
接下来考虑连边。
方法 $1$
首先我们想到,在第一棵线段树上跑 update 时,对于每一个最终得到的区间,都向第二棵连边权为 \(1\) 边。因为区间数最多差不多 \(\log n\),所以 \(m\) 次连边的时间复杂度为 \(O(m \log^2 n)\)。
我让 deepseek 帮我算了一下:
即 \(3.584 \times 10^7\) 条边。
然后你求 Dijkstra 的时候你就炸了。显然需要优化。
于是我们牺牲一点空间。
方法 $2$
建立一个新的虚拟节点,将两棵线段树的区间均连过去。
其中,第一棵线段树连向虚拟节点的边权为 \(1\),第二棵线段树为 \(0\)。这样明显是正确的。
所以即可在 \(O(m \log n)\) 的时间内进行 \(m\) 次连边,空间也是 \(O(m \log n)\) 的。
于是我又借助了 deepseek 帮我算了一下 Dijkstra 的时间。发现是 \(O((n+m) \log^2 n)\) 的。在本题 \(\textsf{2.00 s}\) 的时限下,显然可以过。
最大的点在 \(\textsf{1.18 s}\)。
于是用 update1 与 update2 分别把两个线段树在访问区间时连边。
这时我们发现:建图后的线段树+虚拟节点是可以直接跑 Dijkstra 的。
然后就没了。
\(\textsf{code}\) 代码实现
想看就看
用 T2 表示第二棵线段树的偏移量,conpl 表示虚拟点的偏移量。
untree[i] 表示原图上的点 \(i\) 在第一棵线段树上对应的点。
建线段树(图):
#define T2 (n*4)
#define conpl (n*8)
build(1, 1, n);
for(int i = 1; i <= n; i++){
edge[untree[i]+(T2)].emplace_back(untree[i], 0);
edge[untree[i]].emplace_back(untree[i]+(T2), 0);
}
void build(int p, int l, int r){
if(l == r){
untree[l] = p;
return;
}
edge[p].emplace_back(lp(p), 0);
edge[p].emplace_back(rp(p), 0);
edge[lp(p)+(T2)].emplace_back(p+(T2), 0);
edge[rp(p)+(T2)].emplace_back(p+(T2), 0);
int mid = (l + r) >> 1;
build( lp(p), l, mid );
build( rp(p), mid+1, r );
}
每一个虚拟节点编号为 conpl+tot。tot 表示第几个虚拟节点。
for(int i = 1, l1, r1, l2, r2; i <= m; i++){
cin >> l1 >> r1 >> l2 >> r2;
tot++;
update1(1, l1, r1, 1, n);
update2(1, l2, r2, 1, n);
tot++;
update2(1, l1, r1, 1, n);
update1(1, l2, r2, 1, n);
}
update 就不放了,就是在返回时与虚拟节点连边就行了。
注意连边的方向。
update1:
edge[p+(T2)].emplace_back((conpl)+tot, 1);
update2:
edge[(conpl)+tot].emplace_back(p, 0);
$\green{\textsf{AC}}$ 代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+10;
struct Edge {
int to, w;
Edge(int to, int w): to(to), w(w) {}
};
struct pq {
int dis, u;
bool operator>(const pq &A) const {
return dis > A.dis;
}
};
int n, m, root, tot;
int untree[N], dis[12*N];
bool vis[12*N];
vector <Edge> edge[12*N];
priority_queue <pq, vector<pq>, greater<pq> > p;
#define T2 (n*4)
#define conpl (n*8)
int lp(int x){
return x << 1;
}
int rp(int x){
return x << 1 | 1;
}
void build(int p, int l, int r){
if(l == r){
untree[l] = p;
return;
}
edge[p].emplace_back(lp(p), 0);
edge[p].emplace_back(rp(p), 0);
edge[lp(p)+(T2)].emplace_back(p+(T2), 0);
edge[rp(p)+(T2)].emplace_back(p+(T2), 0);
int mid = (l + r) >> 1;
build( lp(p), l, mid );
build( rp(p), mid+1, r );
}
void update1(int p, int x, int y, int l, int r){
if(x <= l && r <= y){
edge[p+(T2)].emplace_back((conpl)+tot, 1);
return;
}
int mid = (l + r) >> 1;
if(x <= mid)
update1( lp(p), x, y, l, mid );
if(y > mid)
update1( rp(p), x, y, mid+1, r );
}
void update2(int p, int x, int y, int l, int r){
if(x <= l && r <= y){
edge[(conpl)+tot].emplace_back(p, 0);
return;
}
int mid = (l + r) >> 1;
if(x <= mid)
update2( lp(p), x, y, l, mid );
if(y > mid)
update2( rp(p), x, y, mid+1, r );
}
void Dij(){
p.push((pq){0, untree[root]});
memset(dis, 0x3f3f3f3f, sizeof dis);
dis[untree[root]] = 0;
while(!p.empty()){
int u = p.top().u;
p.pop();
if(vis[u])
continue;
vis[u] = 1;
for(auto i : edge[u]){
if(dis[i.to] > dis[u] + i.w){
dis[i.to] = dis[u] + i.w;
p.push((pq){dis[i.to], i.to});
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> root;
build(1, 1, n);
for(int i = 1; i <= n; i++){
edge[untree[i]+(T2)].emplace_back(untree[i], 0);
edge[untree[i]].emplace_back(untree[i]+(T2), 0);
}
for(int i = 1, l1, r1, l2, r2; i <= m; i++){
cin >> l1 >> r1 >> l2 >> r2;
tot++;
update1(1, l1, r1, 1, n);
update2(1, l2, r2, 1, n);
tot++;
update2(1, l1, r1, 1, n);
update1(1, l2, r2, 1, n);
}
Dij();
for(int i = 1; i <= n; i++)
cout << dis[untree[i]] << '\n';
return 0;
}
#undef T2
#undef conpl
这么简单的东西,再不会看代码吧。

浙公网安备 33010602011771号