1 条题解
-
0
自动搬运
来自洛谷,原作者为

Graph
Reject || 错的不是我,而是这个世界搬运于
2025-08-24 23:02:39,当前版本为作者最后更新于2024-11-20 17:07:22,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
Update
- 边权的设定中,第二条将打错的 改为 ,并优化码风和 。
写圆方树板子,纠结了好久,用了一下午才把它弄懂,写篇题解回顾一下。
题意
给一棵仙人掌,每次询问两点之间的最短距离。
题解
我们讲讲如何发明圆方树的人怎么想到的。
首先,如果不考虑环的边权,那么缩点双连通分量,LCA 即可。
那么现在有环的边权,我们不可能像基环树那样对每个点进行操作,因为一条路径上如果全是环,那么复杂度就爆炸了。
于是,我们考虑越过一个环如何处理。
越过一个环,有两种情况,我们分别考虑。
first

注:我们假设 , 代表 在搜索树的深度。
这种情况我们只需要知道环上的点到环上深度最小的点的距离,我们设深度最小的点是 。
那么我们可以建一个虚点(也就是方点,设其为 ),边权有如下设定:
- 的边边权为 。
- 的边权为环上 和 的距离的最小值。
- 同时去除环上的边。
这样我们就可以实现任意 , 的路径最小值等于 和 树上两点之间的距离了。
second

注:我们设 ,。
这种情况对于刚才的建树就失效了,因为刚才只统计了每个节点到深度最小的节点的距离。
但是,我们会发现一个很好的性质:这种情况只会在他们的最近公共祖先处出现一次。
于是我们就可以找到它们最短路径上最近公共祖先“下面”的两个点,我们设它们为 , 和 的距离可以 求,接下来转化为求 的距离。
很简单,预处理每个环的前缀和,找到他们取个最小值即可。
总结
Tarjan 算法缩点是 的,建圆方树是 的。
LCA 我写的树剖,预处理 ,查询 。
综上,时间复杂度为 。
如果使用离线 Tarjan 求 LCA,可以做到优秀的线性复杂度。
关于代码实现
我们可以在 Tarjan 是顺便把圆方树建出来,但是有些复杂,我们结合重要代码(对于一个环建圆方树)看一看。(可以先看后面的代码部分,这里只是突出重点部分)
void help(int x,int y,int val) //val 是非树边边权 { int cur=y,lst=val; while(cur!=x) { sum[cur]+=lst; sum[fa[cur]]+=sum[cur]; lst=vval[cur]; cur=fa[cur]; } int sq=++ext; sum[sq]=sum[x]+lst; // 将所有边权放在方点上 sum[x]=0; // 方便后面处理 cur=y; while(cur!=fa[x]) { int val=min(sum[cur],sum[sq]-sum[cur]); //再来一遍记录边权 Tree::add(cur,sq,val); cur=fa[cur]; } return ; }我们注意到有一行
_tree.sum[x]=0,我们解读一下。是环深度最小的点,目前已经把 的子树遍历完。
后面代码的 实际上要减一个
_tree.sum[x],由于它是 ,所以忽略不计。询问如果最近公共祖先在环内,那么上文中 一定不是 ,因为 的深度比方点小。
如果两个环有交点,那么交点在搜索树上必然会是两个环深度最小的节点之一。
证明比较简单,因为我们是按 DFS 序来确定深度的,访问到的第一个节点就是深度最小的,其余的点深度都比他大,因为其余点后遍历到。
同时,我们会优先处理最小深度较大的环,所以,两个环相交,交点只在一个环中有意义,对于这个交点在深度最小的那一个环,我们会先将它赋值成没有意义的 ,保证后面前缀和的累加不会出问题。
代码
码风真的丑。
#include<bits/stdc++.h> using namespace std; #define int long long const int N=1e5+5; int n,m,q,ext,fa[N]; class eg { public: int to,w; }; namespace Tree { vector<eg>nbr[N]; int dis[N],sum[N],siz[N],hvy[N],sg[N],dep[N],fat[N]; void add(int x,int y,int w) { nbr[x].push_back({y,w}); nbr[y].push_back({x,w}); return ; } void init(int cur,int fa) { dep[cur]=dep[fa]+1; fat[cur]=fa; siz[cur]=1; for(auto dat:nbr[cur]) { int nxt=dat.to,w=dat.w; if(nxt==fa) continue; dis[nxt]=dis[cur]+w; init(nxt,cur); siz[cur]+=siz[nxt]; if(siz[nxt]>siz[hvy[cur]]) hvy[cur]=nxt; } return ; } void dfs(int cur) { if(hvy[fat[cur]]==cur) sg[cur]=sg[fat[cur]]; else sg[cur]=cur; for(auto dat:nbr[cur]) if(dat.to!=fat[cur]) dfs(dat.to); return ; } int lca(int x,int y) { while(sg[x]!=sg[y]) { if(dep[sg[x]]<dep[sg[y]]) x^=y^=x^=y; x=fat[sg[x]]; } if(dep[x]>dep[y]) x^=y^=x^=y; return x; } int findson(int x,int zx) { while(dep[x]-1>dep[zx]) { x=sg[x]; if(fat[x]==zx) return x; x=fat[x]; } if(fat[x]==zx) return x; return hvy[zx]; } int query(int x,int y) { int pos=lca(x,y); if(pos<=n) return dis[x]+dis[y]-2*dis[pos]; else //方点 { int sx=findson(x,pos),sy=findson(y,pos); int now=dis[x]+dis[y]-dis[sx]-dis[sy]; if(sum[sx]>sum[sy]) sx^=sy^=sx^=sy; int val=sum[sy]-sum[sx]; return now+min(val,sum[pos]-val); } }} namespace Graph { using Tree::sum; int dfn[N],low[N],tot,dep[N],vval[N]; vector<eg>nbr[N]; void build() { for(int i=1;i<=m;i++) { int x,y,w; cin>>x>>y>>w; nbr[x].push_back({y,w}); nbr[y].push_back({x,w}); } return ; } void help(int x,int y,int val) //val 是非树边边权 { int cur=y,lst=val; while(cur!=x) { sum[cur]+=lst; sum[fa[cur]]+=sum[cur]; lst=vval[cur]; cur=fa[cur]; } int sq=++ext; sum[sq]=sum[x]+lst; // 将所有边权放在方点上 sum[x]=0; // 方便后面处理 cur=y; while(cur!=fa[x]) { int val=min(sum[cur],sum[sq]-sum[cur]); //再来一遍记录边权 Tree::add(cur,sq,val); cur=fa[cur]; } return ; } void Tarjan(int cur,int fa) { ::fa[cur]=fa; dep[cur]=dep[fa]+1; dfn[cur]=low[cur]=++tot; for(auto dat:nbr[cur]) { int nxt=dat.to,w=dat.w; if(nxt==fa) continue; if(dfn[nxt]==0) { Tarjan(nxt,cur); vval[nxt]=w; //把边权放到深度较深的点上 low[cur]=min(low[cur],low[nxt]); } else low[cur]=min(low[cur],dfn[nxt]); if(low[nxt]>dfn[cur]) //点双连通可以有等于,这里不行,因为是圆点的连边,nxt 有反祖边到 cur 同样形成环 Tree::add(cur,nxt,w); } for(auto dat:nbr[cur]) { int nxt=dat.to,w=dat.w; if(nxt==fa) continue; if(::fa[nxt]!=cur&&dep[cur]<dep[nxt]) //找到非树边 help(cur,nxt,w); } return ; }} signed main() { ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m>>q; ext=n; Graph::build(); Graph::Tarjan(1,0); //建圆方树 Tree::init(1,0); Tree::dfs(1); //Tree chain split while(q--) { int x,y; cin>>x>>y; cout<<Tree::query(x,y)<<"\n"; } return 0; }
欢迎提问。
- 1
信息
- ID
- 10192
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者