仙人掌

一.定义

任意一条边至多出现在一条简单回路的无向连通图。

二.思路

给定一仙人掌图,多次询问两点最短距离。

首先,如果是一棵树,是很好处理的,dis=d[u]+d[v]2d[lca]

然后,它给的不是一棵树,那怎么办呢?我们想把它变成一棵树。我们引入一个知识点:圆方树。

怎么把仙人掌转化为圆方树?

注:圆方树是有向树,父指向子。
(1)任取一点作为根节点。
确定了根节点后,我们可以发现每一个环中都有一个点到根节点的距离最近,这个点就是这个环的老大。(根所在的环,根就是那个老大)

(2)把环变形。怎么变形?
现在环上的所有点都是圆点,我们建立一个方点,从老大向方点连一条边,从方点向环上其余点连一条边。那边权是什么样子的?老大到方点的边权置为0,方点到其他点的距离置为在原环上这个点到老大的最短距离。这样,我们的环就变成了一颗树。

(3)这样我们就把仙人掌转化成了一个有向树。剩下的问题就只和树有关系了。

(4)新的问题又来了,树上老大到小弟的最短距离等于环上老大到小弟的距离,这点毋庸置疑,那一个环上的节点之间的最短距离是否等于树上两点的距离呢?答案是否定的(画一个环理解一下)。

p=lca(x,y)
Ⅰ.p 是圆点
此时,仙人掌中两点的最短距离=圆方树上两点的最短距离。距离就是树上两点距离啦。

Ⅱ.p 是方点
此时,仙人掌中两点的最短距离不一定等于圆方树上两点的最短距离。那如何求?

首先可以知道 xy 一定在一个环上交汇(因为一个方点指向的点在一个同一个环上)。记与环交汇的点记为 xcyc(假如求出来了)。此时,xy的最短距离分为三段:xxcxcycyyc
两边的树上求距离就好了,中间的有两种走法,取最小值即可。

三.实现

这里我们需要用到边双连通分量来求出环。
回顾一下边双连通分量的特征:
任意两点之间都存在两条及以上不相交的路径(不相交:边不能相交)。

老大可以快速找到,第一个被遍历的点就是嘛。

因为一个边双连通分量中可能包含多个环,而我们要把每个环单独求出来,所以我们需要对 Tarjan 做一个简单的变形->一个点被遍历后,看有没有从它出去的环。然后环就求出来了,建圆方树就好了。

边权怎么求?一个点到老大两种路径的最小值。
前缀和思想。
sk 表示按顺时针方向从老大到 k 的距离,st 表示环的总长度,这样就可以O(1) 求环上两点最短距离了。

然后树就建好了,然后就可以树上处理啦。

时间复杂度:O(NlogN)

四.代码

点击查看代码

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 w
f;
}
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;

}

posted @   Travller  阅读(159)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示