最短路

这是仙人掌的模板题,仙人掌不能有自环,但是可以有重边。多颗仙人掌组成的图叫做沙漠。将仙人掌的每个环缩成一个点之后,就会形成树

仙人掌转树要利用圆方树:

①.任选一个点为根
②.此时每个环有且仅有唯一一个点到根的距离最近。然后将环中的点分类,离根节点最近的点叫“头”,剩余的点作为一类。接下来要将环变形。对于一个环,新建立一个方点,然后从头向方点连边(权值为0),方点向环上剩余的点连边(设某个剩余的点为x,权值为“头”到x的最短距离),于是就将一个环拆成了一棵树,如下

image

image

③.分类讨论。一个自然的想法就是求x,y在圆方树上的最近公共祖先p,然后在树上求两者的最短距离。如果说p是圆点的话是成立的(此时x,y要么不在一个环里且x,y之间的(在原图的)路径必经过p或者x,y在一个环里且xy就是p);如果p是方点的话,就说明x,y在原图的路径会先走到同一个环上的两个非头点(因为圆方树中p为方点,就说明从两个不是交汇环的非头点走到p),然后再在这个环上从一个非头点走到另一个非头点。前者可以用p是圆点的思路求,后者求出环上两条路径取更小值就好了。举个例子

image

然后对应到圆方树上的x,y就清楚了

以上就是仙人掌转圆方树的三个步骤,都是这么做的

实现细节:

一.原图当中一个环就是一个v-DCC

二.找头的时候,就是从根节点直接遍历,遍历到的第一个点就是头

上面的写法是狭义圆方树,其实更一般的写法是广义圆方树,见OI-wiki;形象的图的话其实可以这么看:先将原图按照tarjan缩点成v-DCC,就长成了蓝书上面的那个图,然后圆方树相当于不让割点单独成点,然后用方点连接一个v-DCC的所有点,割点连接不同的v-DCC;做题步骤还是一样的,但是广义圆方树由于一条边连接的一定是一个圆点和一个方点,更好分类讨论。这道题目具体来说:定义一个环的头是dfn最小的点(形象来看,也就是蓝书上面那个缩点之后的图深度最小的割点),方点与头连的边权为0,与某个剩余点x连的边权为x到头的最短距离;询问x,y的时候,先找到两者在圆方树上的最近公共祖先p,如果p是圆点,那么x,y在原图的最短距离就是圆方树上两者的距离,如果p是方点,那么x,y在原图上的路线就会先走到方点所代表的环(也就是一个v-DCC)的两个非头点,然后我们计算这两个非头点的最短距离就好了,具体见下面的代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e4+10,M=2e4+10;
int n,m,q;
int End[2][M<<2],Next[2][M<<2],Last[2][N<<1],Len[2][M<<2],cnt[2];
int dfn[N<<1],visitime,low[N<<1],val[N],dis[N],Dis[N<<1],ringlen[N<<1];
int tot,tp,st[N<<1];
int deep[N<<1];
int f[N<<1][30];
void add(int x,int y,int d,bool op)
{
	End[op][++cnt[op]]=y,Next[op][cnt[op]]=Last[op][x],Len[op][cnt[op]]=d,Last[op][x]=cnt[op];
}
void tarjan(int x,int lastedge)
//这里要像找桥边一样判断重边
//因为仙人掌其实是可以有重边的
//比如两个点之间有两条边,仍然不与仙人掌的定义矛盾 
{
    dfn[x]=low[x]=++visitime;
    st[++tp]=x;
    for(int i=Last[0][x];i;i=Next[0][i])
    {
        if((i^1)==lastedge) continue;
        int v=End[0][i];
        if(!dfn[v])
        {
            val[v]=Len[0][i];//val是环上最后一条边的权值 
            dis[v]=dis[x]+Len[0][i];//dis是搜索树上节点到根的距离 
            tarjan(v,i);
            low[x]=min(low[x],low[v]);
            if(dfn[x]<=low[v])//此时找到一个v-DCC 
            {
                tot++;//增加一个方点 
                ringlen[tot]=val[st[tp]]+dis[st[tp]]-dis[x];
                //注意此时栈中节点的排列顺序的意义 
				//从栈顶到x就是这个环的所有点
				//而且是按照顺序的
				//也就是说环就为st[tp]-st[tp-1]-...-x-st[tp] 
				//于是可以计算出环的长度 
                int z;
                do
                {
                    z=st[tp--];
                    int temp=min(ringlen[tot]-(dis[z]-dis[x]),dis[z]-dis[x]);
                    //点到头的最短距离 
                    add(tot,z,temp,1),add(z,tot,temp,1);
                }while(z!=v);
                add(x,tot,0,1),add(tot,x,0,1);
                //别忘了头和方点之间也要连边 
            }
        }
        else if(dfn[v]<dfn[x])
		//如果没有自环,可以直接else
		//有自环的话,要加上if(dfn[v]<dfn[x])
		//此时访问的是一条非树边的返祖边,肯定也就找到了环
		//所以要更新val,因为是环的最后一条边 
        {
            low[x]=min(low[x],dfn[v]);
            val[x]=Len[0][i];
        }
    }
}
void deal_first(int u,int father)
{
    deep[u]=deep[father]+1;
    for(int i=0;i<=15;i++)
    f[u][i+1]=f[f[u][i]][i];
    for(int i=Last[1][u];i;i=Next[1][i])
    {
        if(End[1][i]!=father)
        {
            f[End[1][i]][0]=u;
            Dis[End[1][i]]=Dis[u]+Len[1][i];
            deal_first(End[1][i],u);
        }
    }
}
pair<int,int> LCA(int x,int y)//second为-1表示lca为圆点 
{
    if(x==y) return make_pair(x,-1);//很重要
    if(deep[x]<deep[y]) swap(x,y);
    for(int i=15;i>=0;i--)
    {
        if(deep[f[x][i]]>=deep[y])
        x=f[x][i];
        if(x==y) return make_pair(x,-1);
        if(deep[x]==deep[y]) break;
    }
    for(int i=15;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    if(f[x][0]<=n) return make_pair(f[x][0],-1);
    else return make_pair(x,y);//注意返回的是x和y不是两者的父亲 
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	tot=n;//tot表示圆方树的节点总个数 
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w,0),add(v,u,w,0);
		//0表示原图的边,1表示圆方树的边 
	}
	tarjan(1,0);//这道题目没有孤立点,所以许多写法都简化了一下
	deal_first(1,0);//预处理圆方树的LCA 
	while(q--)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		pair<int,int> p=LCA(u,v);
		if(p.second==-1) printf("%d\n",Dis[u]+Dis[v]-2*Dis[p.first]);
		else
		{
			int temp=min(ringlen[f[p.first][0]]-abs(dis[p.first]-dis[p.second]),abs(dis[p.first]-dis[p.second]));
			printf("%d\n",Dis[u]-Dis[p.first]+Dis[v]-Dis[p.second]+temp);
		}
	}
    return 0;
}
posted @   最爱丁珰  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示