包括删边,删点,修改边权,加边,查询最短路一类问题。
一般图多源最短路
一般图的多源的删边最短路由于受到 Floyd O(n3) 的限制,做法一般比较单一。
删边,多源最短路
题目
ABC375F Road Blocked
给定一个有 n 个点,m 条边的带权无向图,有 q 个查询,类型如下:
1 i
:删除第 i 条边;
2 x y
:求 x 到 y 的最短路长度(无法到达输出 -1
)。
数据范围:n≤300,m≤(2n),删边不超过 300 次。
解法
看到删边不超过 300 次启发我们使用 Floyd O(n3) 解决这个问题。Floyd 能处理新加入一条边,但是比较难做删边,所以我们离线之后倒过来处理,就只需要加边和求任意两点最短路。
怎么用 Floyd 更新一条边对多源最短路的贡献?
假设这条边是 (u,v),长度为 w,di,j 表示 i,j 之间的最短路。
因为更新的最短路只会经过 (u,v) 一次,所以我们用 di,u+dv,j+w 更新 di,j(反向同理)。
这个更新顺序不会影响答案,因为 di,u+dv,j+w 的 d 值是不经过 (u,v) 的(因为这条边只会走一次),直接 O(n2) 处理删边就做完了。
(不难发现这个可以套一个线段树分治,达成 O(n2qlogq) 动态删边加边多源最短路,这也引出了下一个问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include<bits/stdc++.h> using namespace std;
using ll=long long; const int N = 305; ll d[N][N];
struct Edge{int u,v,w;}e[N*N]; bool will_be_deleted[N*N]; const int M = 2e5+5; int op[M][3];
int main(){ ios::sync_with_stdio(0);cin.tie(0);
memset(d,0x3f,sizeof(d)); int n,m,q; cin>>n>>m>>q; for(int i=1;i<=m;i++){ cin>>e[i].u>>e[i].v>>e[i].w; }
for(int i=1;i<=q;i++){ int o;cin>>o; op[i][0]=o; if(o==1)cin>>op[i][1],will_be_deleted[op[i][1]]=1; else cin>>op[i][1]>>op[i][2]; }
for(int i=1;i<=m;i++){ if(!will_be_deleted[i]){ d[e[i].u][e[i].v]=d[e[i].v][e[i].u]=e[i].w; } }
for(int i=1;i<=n;i++)d[i][i]=0;
for(int k=1;k<=n;k++){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ d[i][j] = min(d[i][j],d[i][k]+d[k][j]); } } }
vector<ll> ans; for(int x=q;x>0;x--){ if(op[x][0]==1){ auto [u,v,w] = e[op[x][1]]; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ d[i][j] = min(d[i][j],d[i][u]+d[v][j]+w); d[i][j] = min(d[i][j],d[i][v]+d[u][j]+w); } } } else{ ans.push_back(d[op[x][1]][op[x][2]]); } }
for(auto it=ans.rbegin();it<ans.rend();it++){ if(*it == d[0][0])*it=-1; cout << *it << '\n'; } return 0; }
|
删点,多源最短路
题目
QOJ9903 最短路径
给定一个有 n 个点,m 条边的带权有向图,有 q 个查询,输出在删除节点 pi 的条件下 si 到 ti 的最短路长度,查询删除节点仅对当前查询有效,不持续。
数据范围:n≤300,m≤(2n),q≤5×105。
简单版:P1119 灾后重建
解法
我们知道,给定一个无向图,加入一个点更新多源最短路可以 O(n2) 完成。
因为在 Floyd 中,假设最外层的变量枚举的集合为 S,则此时 di,j 就储存的是原图一个点集 {i,j}∪S 的子图中 i 到 j 的最短路。(从动态规划角度思考)
因此,加点只需要枚举 i,j,用 dpi,k+dpk,j 更新 dpi,j 即可。
但是删点就比较困难了,这种能加不能删的问题我们考虑用线段树分治解决。在付出一个 log 的代价后,我们就不需要考虑删除的问题了。
还需要注意到一个性质,就是删除的节点最多只有 n 种,所以我们可以把删除节点相同的查询放到一起处理(离线),然后直接线段树分治。
这个线段树分治不需要显式地构建出线段树(当然这么写也是可以的),因为叶子 i 是恰好不包含 i 的,所以每次递归的时候,用右子树的每个顶点去更新最短路,得到的结果给左子树,反之亦然。
复杂度 O(n3logn+q)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include<bits/stdc++.h> using namespace std;
using ll=long long;
const int N = 305; struct Node{int u,v,id;}; vector<Node> qs[N]; const int Q = 5e5+5; ll ans[Q];
int n,q; void solve(int l,int r,vector<vector<ll>> d){ if(l==r){ for(auto [u,v,id]:qs[l])ans[id]=d[u][v]; return; }
auto e = d; int mid = (l+r)>>1; for(int k=l;k<=mid;k++){ for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){ d[i][j] = min(d[i][j], d[i][k]+d[k][j]); } } for(int k=mid+1;k<=r;k++){ for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){ e[i][j] = min(e[i][j], e[i][k]+e[k][j]); } } solve(l,mid,e);solve(mid+1,r,d); }
int main(){ ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>q; vector<vector<ll>> d(n+1,vector<ll>(n+1));
for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ cin >> d[i][j]; } } for(int i=1;i<=q;i++){ int s,t,p; cin>>s>>t>>p; qs[p].push_back({s,t,i}); } solve(1,n,d); for(int i=1;i<=q;i++)cout<<ans[i]<<'\n'; return 0; }
|
一般图单源最短路
删边判断 1 到 n 最短路长度是否变化
题目
ABC375G Road Blocked 2
有一个 n 个点和 m 条边的带权无向图。
对每个 i∈[1,m] 的每一条边,判断删除它之后 1→n 的最短路长度是否发生变化(若不存在路径,称此时最短路长度为 −1)。
数据范围:n,m≤2×105。
解法 1
删除一条边后,最短路长度发生变化的充要条件是:所有最短路都经过它。
- (⇒):证明它的逆否命题,如果有一条最短路没有经过这条边,最短路长度当然不会变。
- (⇐):因为所有最短路都经过它,最短路显然会发生变化。
我们用 dijkstra 能轻易计算出 1→n 有多少条最短路,如果能求出经过一条边的最短路个数就能解决本题。
这也是简单的。分别求出以 1 和 n 为起点的单源最短路,对于一条边 (u,v,w),如果 1→u 最短路长度,v→n 最短路长度和 w 加起来为最短路就说明最短路经过这条边,然后只需要把 1→u 和 v→n 的最短路方案乘起来就是经过这条边的最短路个数了。(反向经过的也一样计算)
需要套一个哈希,因为最短路径个数很多。
参考代码与题解
解法 2
「我不喜欢哈希,能不能来一个确定性做法?」
我们来考虑另一种做法。
接上文,我们已经把条件转化为了所有最短路都经过这条边。
考虑把所有可能最短路上的边抽出来建立一个子图 G0。
这个命题其实等价于它是这个子图 G0 的一个割边(桥)。
- (⇒):证明它的逆否命题,如果不是割边,就存在一条 1→n 的路径,可以发现子图的任何一条 1→n 的简单路径长度都是最短路。
- (⇐):因为是割边,删除后图不连通,假设变成了两个连通块 S,T,不妨设 1∈S,现在需要证明 n∈T 才能导出这个结论。假设 n∈S,则对任意一个 u∈T,因为原图存在路径 1→u 和 u→n,且这两个路径一定不相交,则至少要删掉两条边,矛盾,即证。所以不存在 1→n 的路径,最短路长度自然改变。
求可能最短路还是从 1 和 n 跑两遍 dijkstra,求割边用 Tarjan 就好了。
参考:连通性问题(2)- 割点和桥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include<bits/stdc++.h> using namespace std;
using ll=long long;
const int N = 2e5+5; struct Edge{int v,w;}; vector<Edge> G[N];
ll d1[N],d2[N]; tuple<int,int,int> edge[N];
bool vis[N];
inline void dijkstra(int s,ll* d){ memset(vis,0,sizeof(vis)); priority_queue<pair<ll,int>> pq; pq.push({0,s}); d[s]=0; while(pq.size()){ int u = pq.top().second;pq.pop(); if(vis[u])continue; vis[u]=1; for(auto [v,w]:G[u]){ if(d[v] > d[u]+w){ d[v] = d[u]+w; pq.push({-d[v],v}); } } } }
vector<pair<int,int>> G2[N]; int dfn[N],low[N],tot; bool is_bridge[N]; void tarjan(int u,int fa){ dfn[u]=low[u]=++tot; for(auto [v,id]:G2[u]){ if(v==fa)continue; if(!dfn[v]){ tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u])is_bridge[id]=1; } else low[u]=min(low[u],dfn[v]); } }
int main(){ ios::sync_with_stdio(0);cin.tie(0);
memset(d1,0x3f,sizeof(d1)); memset(d2,0x3f,sizeof(d2)); int n,m;cin>>n>>m; for(int i=0;i<m;i++){ int u,v,w;cin>>u>>v>>w; G[u].push_back({v,w});G[v].push_back({u,w}); edge[i] = {u,v,w}; }
dijkstra(1,d1); dijkstra(n,d2);
for(int i=0;i<m;i++){ auto [u,v,w] = edge[i]; if(d1[u]+d2[v]+w == d1[n] || d1[v]+d2[u]+w == d1[n]){ G2[u].push_back({v,i}); G2[v].push_back({u,i}); } }
tarjan(1,1);
for(int i=0;i<m;i++){ if(is_bridge[i])cout<<"Yes\n"; else cout<<"No\n"; } return 0; }
|
删边求 1 到 n 最短路长度
是上一题的加强版。
题目
P2685 [TJOI2012] 桥(弱化版:P1186 玛丽卡)
有一个 n 个点和 m 条边的带权无向图。
对每个 i∈[1,m] 的每一条边,判断删除它之后 1→n 的最短路长度最长是多少,并求出有多少条边能取到这个最大值。
数据范围:n≤105,m≤2×105,有重边和自环,保证无论删除哪一条边都存在 1→n 的路径。
解法
先求出 1→n 的任意一条最短路,如果删除的边不在最短路上显然最短路长度不变(不代表不是最大值)。
成为答案的最短路一定至少经过一条最短路以外的边(除了删除任意边最短路长度都不变的情况,这时候特判),我们枚举这条边。
设 d1u 表示 1→u 的最短路长度,d2v 表示 v→n 的最短路长度。
对于一条不在最短路上的边 (u,v,w),我们考虑删除最短路上的哪些边时,这条边有可能变成答案。先考虑不删除任何边的时候,强制经过这条边的最短路长度是 d1u+d2v+w。(反向边也要做,后文所有过程 (v,u,w) 都要再做一次)
如果建出以 1 和 n 为根的任意两颗最短路树(但是强制包含我们钦定的那条最短路,这是可以做到的)。
考察上文长度为 d1u+d2v+w 的路径,除了经过这条边,还经过两段 1→u 和 v→n 的路径,1→u 的最短路和我们钦定的最短路有一段公共前缀,v→n 的最短路有一段公共后缀。
记 lu 表示 1→n 的最短路径上第一条不在最短路树上 1→u 最短路径的边(也就是公共前缀的后一个),ru 表示 1→n 的最短路径上最后一条不在另一颗最短路树上 v→n 的边。
如果删除的边在 [lu,rv] 之间,那么我们发现包括 (u,v,w) 的那条最短路完全没有受到影响,因此用 d1u+d2v+w 更新删除 [lu,rv] 的可能最短路长度。(取所有更新过的最小值)
接下来还要证明如果删除的边在 [lu,rv] 之外,一定有一条最短路不经过 (u,v,w),我们就可以用线段树做一个区间取最小值解决本题了。(因为所有有可能经过 (u,v,w) 的最短路我们都更新到了)
这个结论是对的。假设删除的边在 [lu,rv] 之外,存在一条最短路 1→u→v→n。如果 ru≥lu−1,那么 1→u 后面可以直接连到 u→n 不经过其他非树边。如果 ru<lu−1,那么这种情况是不存在的。画图就可以知道没有这样子的最短路树。
所以做完了。复杂度 O(mlogm)。
注意如果最后线段树的最大值和原最短路一样,那么可能删除的边应该是 m 而不是 1→n 的最短路经过边数,因为这时候删除非最短路边也会达到最值。
在实现细节方面可以注意两点:
- 事实上,这问题需要的数据结构是区间取最小值,单点查询(先全部修改再全部查询),可以扫描一遍用
multiset
解决,会更好写一点。
- 有关这两颗最短路树的构建,因为已知一条 1→n 的链,可以从这个链上的每一个点出发 bfs(不能经过这条链上的点),这样搜索到的节点在最短路树上的 LCA 就是当前节点,就能方便求出
l
和 r
数组,同时也保证了最短路树包含了我们的最短路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include<bits/stdc++.h> using namespace std;
const int N = 2e5+5; vector<tuple<int,int,int>> G[N]; tuple<int,int,int> edges[N]; int d1[N],d2[N]; bool vis[N];
void dijkstra(int s,int* d){ memset(d,0x3f,N*sizeof(int)); memset(vis,0,sizeof(vis)); priority_queue<pair<int,int>> pq; pq.push({0,s}); d[s]=0; while(pq.size()){ int u = pq.top().second;pq.pop(); if(vis[u])continue; vis[u]=1; for(auto [v,w,_]:G[u]) if(d[v] > d[u]+w) d[v]=d[u]+w, pq.push({-d[v],v}); } }
int l[N],r[N];
bool in_path[N],used[N];
void bfs(int s,int val, int* d,int* arr){ queue<int> q;q.push(s); while(q.size()){ int u = q.front(); q.pop(); arr[u] = val; for(auto [v,w,id]:G[u]){ if(in_path[v] || arr[v])continue; if(d[v]==d[u]+w)q.push(v); } } }
vector<int> add[N],del[N]; int main(){ ios::sync_with_stdio(0);cin.tie(0); int n,m; cin>>n>>m; for(int i=1;i<=m;i++){ int u,v,w; cin>>u>>v>>w; edges[i]={u,v,w}; G[u].push_back({v,w,i});G[v].push_back({u,w,i}); } dijkstra(1,d1); dijkstra(n,d2); vector<int> path = {1}; int now = 1; in_path[1] = 1; while(now != n){ for(auto [v,w,id]:G[now]){ if(d1[now] + d2[v] + w == d1[n]){ now = v; path.push_back(v); in_path[now] = 1; used[id] = 1; break; } } }
for(int i=0;i<path.size();i++)bfs(path[i], i+1, d1, l); for(int i=path.size()-1;i>=0;i--)bfs(path[i], i, d2, r);
for(int i=1;i<=m;i++){ if(used[i])continue; auto [u,v,w] = edges[i]; if(l[u]<=r[v]){ add[l[u]].push_back(d1[u]+d2[v]+w); del[r[v]+1].push_back(d1[u]+d2[v]+w); } swap(u,v); if(l[u]<=r[v]){ add[l[u]].push_back(d1[u]+d2[v]+w); del[r[v]+1].push_back(d1[u]+d2[v]+w); } }
int minv = 0, mincnt = 0; multiset<int> val; for(int i=1;i<path.size();i++){ for(auto x:add[i])val.insert(x); for(auto x:del[i])val.erase(val.find(x)); int v = *val.begin(); if(v>minv)minv=v,mincnt=1; else if(v==minv)++mincnt; }
if(minv == d1[n])mincnt = m; cout << minv << ' ' << mincnt << '\n'; return 0; }
|
改一条边权查询 1 到 n 最短路
题目
CF1163F. Indecisive Taxi Fee
有一个 n 个点和 m 条边的带权无向图。
查询把第 i 条边边权改成 j 后,1 到 n 的最短路长度,查询之间独立。
数据范围:n,m,q≤2×105。
解法
是上一题的加强版,因此请先确定你会上一题再来完成本题。
两题思路并没有明显区别,做法也很相似。
延续上一问的做法,随便找到一条最短路,以及 lu,ru,分别表示 1→n 的最短路径上第一条不在最短路树上 1→u 最短路径的边(也就是公共前缀的后一个)和 1→n 的最短路径上最后一条不在另一颗最短路树上 v→n 的边。
设 d1u 表示在原图上 1→u 的最短路长度,d2v 表示 v→n 的最短路长度。
分类讨论修改的边:
- 修改的边不在最短路径上:
最短路取 d1u+d2v+w 和 d1n 的最小值。
- 修改的变在最短路径上:
求把这条边断掉求最短路,和原最短路修改之后的较小值。删边最短路已经在上一题中很详细的阐述了,用线段树或 multiset
即可。(注意这道题 multiset
可能是空的,此时应采用 ∞)
注意:这道题没有保证整个图连通,所以对于不和 1,n 连通的些边应该忽视。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include<bits/stdc++.h> using namespace std; using ll = long long;
const int N = 2e5+5; vector<tuple<int,int,int>> G[N]; tuple<int,int,int> edges[N]; ll d1[N],d2[N]; bool vis[N];
void dijkstra(int s,ll* d){ memset(d,0x3f,N*sizeof(ll)); memset(vis,0,sizeof(vis)); priority_queue<pair<ll,int>> pq; pq.push({0,s}); d[s]=0; while(pq.size()){ int u = pq.top().second;pq.pop(); if(vis[u])continue; vis[u]=1; for(auto [v,w,_]:G[u]) if(d[v] > d[u]+w) d[v]=d[u]+w, pq.push({-d[v],v}); } }
int l[N],r[N];
bool in_path[N]; int edge_id[N];
void bfs(int s,int val, ll* d,int* arr){ queue<int> q;q.push(s); while(q.size()){ int u = q.front(); q.pop(); arr[u] = val; for(auto [v,w,id]:G[u]){ if(in_path[v] || ~arr[v])continue; if(d[v]==d[u]+w)q.push(v); } } }
vector<ll> add[N],del[N]; ll cutd[N]; int main(){ ios::sync_with_stdio(0);cin.tie(0); int n,m,q; cin>>n>>m>>q; for(int i=1;i<=m;i++){ int u,v,w; cin>>u>>v>>w; edges[i]={u,v,w}; G[u].push_back({v,w,i});G[v].push_back({u,w,i}); } dijkstra(1,d1); dijkstra(n,d2); vector<int> path = {1}; int now = 1; in_path[1] = 1; while(now != n){ for(auto [v,w,id]:G[now]){ if(d1[now] + d2[v] + w == d1[n]){ edge_id[id] = path.size(); now = v; path.push_back(v); in_path[now] = 1; break; } } }
memset(l,-1,sizeof(l));memset(r,-1,sizeof(r));
for(int i=0;i<path.size();i++)bfs(path[i], i+1, d1, l); for(int i=path.size()-1;i>=0;i--)bfs(path[i], i, d2, r);
for(int i=1;i<=m;i++){ if(edge_id[i])continue; auto [u,v,w] = edges[i]; if(!~l[u])continue;
if(l[u]<=r[v]){ add[l[u]].push_back(d1[u]+d2[v]+w); del[r[v]+1].push_back(d1[u]+d2[v]+w); } swap(u,v); if(l[u]<=r[v]){ add[l[u]].push_back(d1[u]+d2[v]+w); del[r[v]+1].push_back(d1[u]+d2[v]+w); } }
multiset<ll> val; for(int i=1;i<path.size();i++){ for(auto x:add[i])val.insert(x); for(auto x:del[i])val.erase(val.find(x)); cutd[i] = val.size() ? *val.begin() : LLONG_MAX; }
while(q--){ int t,new_w; cin>>t>>new_w; auto [u,v,w] = edges[t]; if(!~l[u])cout<<d1[n]<<'\n'; else if(edge_id[t])cout << min(cutd[edge_id[t]],d1[n] + new_w - w) << '\n'; else cout << min({d1[v]+d2[u]+new_w,d1[u]+d2[v]+new_w, d1[n]}) << '\n'; }
return 0; }
|
多次边权加 1,查询 1 到 u 最短路
题目
CF843D. Dynamic Shortest Path
有一个 n 个点和 m 条边的带权有向图。
支持两种操作:
- 给出 c 条边,将它们的边权加 1。
- 求 1→u 的最短路,如果不存在输出
-1
。
数据范围:n,m≤1×105,q≤2000,∑c≤106。
解法
我们先介绍引入势能的最短路。(和在 Johnson 全源最短路里的势能是一样的)
给图上每一个点赋势能 hu,将所有从 u→v 的边边权从 w 变为 w+hu−hv,然后在新图上从 1 开始跑最短路。可以证明,无论势能怎么取,新图上的最短路距离总满足 du′=du+h1−hu(考虑势能抵消),于是就可以用新图的最短路来求出原图的最短路。
现在我们有 c 条边需要边权加 1。如果我们用加 1 之前的最短路距离 d0u 作为点 u 的势能,我们能发现一些关键性质:
- 任何新图的边权非负,因为 w+hu−hv≥w0+hu−hv,而 d0v≤d0u+w0,得证。于是可以用 dijkstra 来求最短路。
- 新图的最短路最大值为 min(c,n−1),因为考虑原图的最短路树,最短路树上的边边权最多为 1,所以最短路的上界是 n−1,而只加了 c 条边最短路最多增加 c。
因此,只要在 O(n+m+W) 解决最短路最大值为 W 图的 dijkstra 就可以在 O(q(n+m)+mlogm) 的复杂度内解决本题(对于每个操作 1 都跑一遍 dijkstra 更新最短路,然后 O(1) 回答操作 2)。
这是很容易做到的。普通 dijkstra 的瓶颈在于优先队列的 log,因为值域很小,我们可以用一个桶来代替优先队列,每次从桶内取出元素即可。
有点卡常,如果你要写建议小心一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include<bits/stdc++.h> using namespace std; using ll=long long; int read(){ int f=1,x=0;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; }
const int N = 1e5+5; const ll INF = 0x3f3f3f3f3f3f3f3f; pair<int,int> edges[N]; int head[N],nxt[N],tot; void add_edge(int u){ ++tot; nxt[tot]=head[u]; head[u]=tot; }
void dijkstra(int s,ll* d){ bitset<N> vis; memset(d,0x3f,N*sizeof(*d)); priority_queue<pair<ll,int>> pq; pq.push({0,s}); d[s]=0; while(pq.size()){ int u = pq.top().second;pq.pop(); if(vis[u])continue; vis[u]=1; for(int o=head[u];o;o=nxt[o]){ auto [v,w] = edges[o]; if(d[v] > d[u]+w) d[v]=d[u]+w, pq.push({-d[v],v}); } } }
queue<int> buc[N];
void dijkstra_with_bucket(int s,int mx,const ll *h,int *d){ memset(d,0x3f,N*sizeof(*d)); d[s]=0; buc[0].push(s); for(int i=0;i<=mx;i++){ while(buc[i].size()){ auto u = buc[i].front(); buc[i].pop(); if(d[u] < i)continue; for(int o=head[u];o;o=nxt[o]){ auto [v,w] = edges[o]; ll wh = h[u]-h[v]+w; if(d[u]+wh<min(mx+1,d[v]))d[v]=d[u]+wh,buc[d[v]].push(v); } } } }
ll d[N];int newd[N]; int main(){ int n=read(),m=read(),q=read(); for(int i=1;i<=m;i++){ int u=read(),v=read(),w=read(); add_edge(u); edges[i]={v,w}; } dijkstra(1,d); while(q--){ int op=read(); if(op==1){ int u=read(); cout << (d[u]==INF?-1:d[u]) << '\n'; } else{ int cnt=read(), mx = min(cnt, n-1); while(cnt--)edges[read()].second++; dijkstra_with_bucket(1, mx, d, newd); for(int i=1;i<=n;i++)d[i]+=(d[i]==INF?0:newd[i]); } } return 0; }
|
有限制图的多源最短路
一般图显然是不可做的(因为不弱于静态多源最短路 O(n3) 或 O(nmlogm))。但是如果图有一些特殊的性质(比如是树/基环树),这个问题还是可以完成的。
基环树的动态多源最短路
题目
P4949 最短距离
给出一个 n 个点 n 条边的带权无向连通图,支持 q 次操作,每次为两种之一:
- 修改第 x 条边的长度为 y;
- 查询点 x 到点 y 的最短距离。
数据范围:n≤105+6,m≤105。
解法
原图是一个基环树。
基环树有两种处理方法:
- 看作是一个环,环上每一个点都挂了一棵树;
- 看作一棵树多一条边。
本题这两种做法都可以,此处提供更好写的第 2 种方法,有关方法 1 可以查看原题的题解区。
假设是一棵树和额外的边 (u,v,w)。
从节点 x→y 一共有三种情况:
- 从树上 x→y;
- 从树上 x→u,经过 (u,v,w) 再从树上 v→y;
- 从树上 x→v,经过 (v,u,w) 再从树上 u→y。
问题就转化成:修改树的边权,求树上两点路径距离。
这是经典的树链剖分,可以 O(log2n) 完成,我们再介绍一个单 log 的写法。
先把边权下放到点。我们要做的是修改点权,查询路径上除 LCA 的点权和。做一个树上前缀和,di 表示 i 到根的点权和,修改就是一个子树加,查询是 du+dv−2dLCA(u,v),可以树状数组维护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| #include<bits/stdc++.h> using namespace std;
using ll=long long;
const int N = 1e5+10; int dep[N],dfn[N],sz[N],tot,fa[N][20]; vector<pair<int,int>> G[N];
int low[N],ex_id; tuple<int,int,int> edges[N];
void dfs(int u,int f){ dep[u] = dep[f] + 1; sz[u] = 1; dfn[u] = ++tot; fa[u][0] = f; for(int i=1;i<=__lg(dep[u]);i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(auto [v,id]:G[u]){ if(v==f)continue; dfs(v,u);low[id]=v; sz[u]+=sz[v]; } } int lca(int u,int v){ if(dep[u]>dep[v])swap(u,v); while(dep[u]<dep[v])v = fa[v][__lg(dep[v]-dep[u])]; if(u==v)return u; for(int i=__lg(dep[u]);i>=0;i--){ if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i]; } return fa[u][0]; }
struct DSU{ int fa[N]; void init(int n){for(int i=1;i<=n;i++)fa[i]=i;} int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} void unite(int i,int j){fa[find(i)]=find(j);} }dsu;
int n,q; struct BIT{ ll t[N]; void add(int x,ll v){ if(!x)return; for(;x<=n;x+=x&-x)t[x]+=v; } ll query(int x){ ll ans = 0; for(;x;x-=x&-x)ans+=t[x]; return ans; }
void add_u(int u,int x){ add(dfn[u],x); add(dfn[u]+sz[u],-x); } ll query_path(int u,int v){ int w = lca(u,v); return query(dfn[u])+query(dfn[v])-2*query(dfn[w]); } }bit;
int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n>>q; dsu.init(n); for(int i=1;i<=n;i++){ int u,v,w;cin>>u>>v>>w; edges[i]={u,v,w}; if(dsu.find(u)==dsu.find(v))ex_id = i; else{ dsu.unite(u,v); G[u].push_back({v,i});G[v].push_back({u,i}); } }
dfs(1,0); for(int i=1;i<=n;i++){ auto [u,v,w] = edges[i]; if(low[i])bit.add_u(low[i], w); }
while(q--){ int op,x,y; cin>>op>>x>>y; if(op==1){ auto& [u,v,w] = edges[x]; if(low[x])bit.add_u(low[x],y-w); w=y; } else{ auto [eu,ev,ew] = edges[ex_id]; int ans = min({ bit.query_path(x,y), bit.query_path(x,eu)+ew+bit.query_path(ev,y), bit.query_path(x,ev)+ew+bit.query_path(eu,y), }); cout << ans << '\n'; } } return 0; }
|
CF838B Diverging Directions
题目
CF838B Diverging Directions
给出一个 n 个点 2n−2 条边的带权有向图,图的结构为:
- 前 n−1 条边构成一个以 1 为根的外向树;
- 后 n−1 条边是 i (2≤i≤n) 连向 1 的有向边。
你需要支持 q 次操作,每次为两种之一:
- 修改第 x 条边的长度为 y;
- 查询点 x 到点 y 的最短距离。
数据范围:n,q≤2×105。
解法
求 x→y 的最短路。
- 如果在树上 x 是 y 的祖先,那么从 x 一路往下到 y 就是最优解。
- 否则,可以证明最短路一定是 x 往下走一段(可以不走),然后用第二类边回到 1,最后一直往下到 y。
设 d1u 表示树上 1→u 的边权和,d2u 表示 u→1 的边权。
对于修改,修改第一类边边权就是子树 d1 加,修改第二类边边权就是单点 d2 加。
第一种最短路的边权就可以写作 d1y−d1x,容易解决。
第二种最短路的边权是 minv{d1v−d1x+d2v+d1y}(v 是 u 的后代)。d1y−d1x 是定值,分离出来,就要求子树内 d1v+d2v 最小值,用区间加,区间最小值线段树解决。
还需要求 LCA,选择喜欢的方法即可。(后面代码是倍增)
复杂度 O((n+q)logn)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| #include<bits/stdc++.h> using namespace std;
using ll=long long; const int N = 2e5+5;
ll d1[N],d2[N]; vector<pair<int,int>> G[N]; int dfn[N],id[N],sz[N],tot,dep[N],fa[N][20]; void dfs(int u,int f){ dep[u] = dep[f] + 1; dfn[u]=++tot;id[tot]=u;sz[u]=1; fa[u][0] = f; for(int i=1;i<=__lg(dep[u]);i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(auto [v,w]:G[u]){ d1[v]=d1[u]+w; dfs(v,u); sz[u]+=sz[v]; } } int lca(int u,int v){ if(dep[u]>dep[v])swap(u,v); while(dep[u]<dep[v])v = fa[v][__lg(dep[v]-dep[u])]; if(u==v)return u; for(int i=__lg(dep[u]);i>=0;i--){ if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i]; } return fa[u][0]; }
ll t[N*4],tag[N*4]; void build(int o,int l,int r){ if(l==r){ t[o] = d1[id[l]]+d2[id[l]]; return; } int lch=o<<1,rch=o<<1|1; int mid=(l+r)>>1; build(lch,l,mid);build(rch,mid+1,r); t[o]=min(t[lch],t[rch]); } void add(int o,ll k){ t[o] +=k; tag[o]+=k; } void pushdown(int o){ if(tag[o]){ int lch=o<<1,rch=o<<1|1; add(lch,tag[o]),add(rch,tag[o]); tag[o]=0; } } void modify(int o,int l,int r,int ql,int qr,ll k){ if(ql<=l&&r<=qr)return add(o,k); int lch=o<<1,rch=o<<1|1; int mid=(l+r)>>1; pushdown(o); if(ql<=mid)modify(lch,l,mid,ql,qr,k); if(qr>mid) modify(rch,mid+1,r,ql,qr,k); t[o]=min(t[lch],t[rch]); } ll query(int o,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr)return t[o]; int lch=o<<1,rch=o<<1|1; int mid=(l+r)>>1; pushdown(o); if(qr<=mid)return query(lch,l,mid,ql,qr); if(ql>mid) return query(rch,mid+1,r,ql,qr); return min(query(lch,l,mid,ql,qr),query(rch,mid+1,r,ql,qr)); }
struct Edge{ int u,v,w; }edges[N*2];
int main(){ ios::sync_with_stdio(0);cin.tie(0); cout<<fixed;
int n,q; cin>>n>>q; for(int i=1;i<n;i++){ int u,v,w; cin>>u>>v>>w; edges[i]={u,v,w}; G[u].push_back({v,w}); } for(int i=n;i<=2*n-2;i++){ int u,v,w; cin>>u>>v>>w; edges[i]={u,v,w}; d2[u]=w; }
dfs(1,1); build(1,1,n);
while(q--){ int op,x,y; cin>>op>>x>>y; if(op==1){ if(x<=n-1){ auto &[_,u,w] = edges[x]; modify(1,1,n,dfn[u],dfn[u]+sz[u]-1,y-w); w=y; } else{ auto &[u,_,w] = edges[x]; modify(1,1,n,dfn[u],dfn[u],y-w); w=y;d2[u]=w; } } else{ ll d1x = query(1,1,n,dfn[x],dfn[x])-d2[x]; ll d1y = query(1,1,n,dfn[y],dfn[y])-d2[y]; if(lca(x,y)==x){ cout << d1y - d1x << '\n'; } else{ cout << d1y + query(1,1,n,dfn[x],dfn[x]+sz[x]-1) - d1x << '\n'; } } } return 0; }
|