仙人掌
一.定义
任意一条边至多出现在一条简单回路的无向连通图。
二.思路
给定一仙人掌图,多次询问两点最短距离。
首先,如果是一棵树,是很好处理的,
然后,它给的不是一棵树,那怎么办呢?我们想把它变成一棵树。我们引入一个知识点:圆方树。
怎么把仙人掌转化为圆方树?
注:圆方树是有向树,父指向子。
(1)任取一点作为根节点。
确定了根节点后,我们可以发现每一个环中都有一个点到根节点的距离最近,这个点就是这个环的老大。(根所在的环,根就是那个老大)
(2)把环变形。怎么变形?
现在环上的所有点都是圆点,我们建立一个方点,从老大向方点连一条边,从方点向环上其余点连一条边。那边权是什么样子的?老大到方点的边权置为0,方点到其他点的距离置为在原环上这个点到老大的最短距离。这样,我们的环就变成了一颗树。
(3)这样我们就把仙人掌转化成了一个有向树。剩下的问题就只和树有关系了。
(4)新的问题又来了,树上老大到小弟的最短距离等于环上老大到小弟的距离,这点毋庸置疑,那一个环上的节点之间的最短距离是否等于树上两点的距离呢?答案是否定的(画一个环理解一下)。
设
Ⅰ.
此时,仙人掌中两点的最短距离=圆方树上两点的最短距离。距离就是树上两点距离啦。
Ⅱ.
此时,仙人掌中两点的最短距离不一定等于圆方树上两点的最短距离。那如何求?
首先可以知道
两边的树上求距离就好了,中间的有两种走法,取最小值即可。
三.实现
这里我们需要用到边双连通分量来求出环。
回顾一下边双连通分量的特征:
任意两点之间都存在两条及以上不相交的路径(不相交:边不能相交)。
老大可以快速找到,第一个被遍历的点就是嘛。
因为一个边双连通分量中可能包含多个环,而我们要把每个环单独求出来,所以我们需要对 Tarjan 做一个简单的变形->一个点被遍历后,看有没有从它出去的环。然后环就求出来了,建圆方树就好了。
边权怎么求?一个点到老大两种路径的最小值。
前缀和思想。
设
然后树就建好了,然后就可以树上处理啦。
时间复杂度:
四.代码
点击查看代码
include
include
include
include
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w10+ch-'0';
ch=getchar();
}
return wf;
}
const int N=12010,M=N*3;
int n,m,Q,new_n;
int h1[N],h2[N],e[M],w[M],ne[M],idx;
int dfn[N],low[N],cnt;
int s[N],stot[N],fu[N],fw[N],fe[N];
int fa[N][15],depth[N],d[N];
int A,B;
void add(int h[],int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void build_circle(int x,int y,int z){
int sum=z;
for(int k=y;k!=x;k=fu[k]){
s[k]=sum;
sum+=fw[k];
}
s[x]=stot[x]=sum;
add(h2,x,++new_n,0);
for(int k=y;k!=x;k=fu[k]){
stot[k]=sum;
add(h2,new_n,k,min(s[k],sum-s[k]));
}
}
void tarjan(int u,int from){
dfn[u]=low[u]=++cnt;
for(int i=h1[u];~i;i=ne[i]){
int j=e[i];
if(!dfn[j]){
fu[j]=u,fw[j]=w[i],fe[j]=i;
tarjan(j,i);
low[u]=min(low[u],low[j]);
if(dfn[u]<low[j]) add(h2,u,j,w[i]);
}else if(i!=(from^1)) low[u]=min(low[u],dfn[j]);
}
for(int i=h1[u];~i;i=ne[i]){
int j=e[i];
if(dfn[u]<dfn[j]&&fe[j]!=i)
build_circle(u,j,w[i]);
}
}
void dfs_lca(int u,int father){
depth[u]=depth[father]+1;
fa[u][0]=father;
for(int k=1;k<=13;k++)
fa[u][k]=fa[fa[u][k-1]][k-1];
for(int i=h2[u];~i;i=ne[i]){
int j=e[i];
d[j]=d[u]+w[i];
dfs_lca(j,u);
}
}
int lca(int a,int b){
if(depth[a]<depth[b]) swap(a,b);
for(int k=13;k>=0;k--)
if(depth[fa[a][k]]>=depth[b])
a=fa[a][k];
if(a==b) return a;
for(int k=13;k>=0;k--){
if(fa[a][k]!=fa[b][k]){
a=fa[a][k];
b=fa[b][k];
}
}
A=a,B=b;
return fa[a][0];
}
int main(){
n=read(),m=read(),Q=read();
new_n=n;
memset(h1,-1,sizeof(h1));
memset(h2,-1,sizeof(h2));
while(m--){
int a,b,c;
a=read(),b=read(),c=read();
add(h1,a,b,c),add(h1,b,a,c);
}
tarjan(1,-1);
dfs_lca(1,0);
while(Q--){
int a,b;
a=read(),b=read();
int p=lca(a,b);
if(p<=n) cout<<d[a]+d[b]-2*d[p]<<'\n';
else{
int da=d[a]-d[A],db=d[b]-d[B];
int l=abs(s[A]-s[B]);
int dm=min(l,stot[A]-l);
cout<<da+dm+db<<'\n';
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效