测试地址:最短路
做法:本题需要用到圆方树来处理仙人掌。
题目中所给的图称作仙人掌图,即每条边至多在一个环上的图。对于这种图,我们一般把它转化成树后,将树上的某些算法修改后来解决仙人掌上的问题。常用的一种转化方式就是圆方树。
在圆方树中,一个圆点表示原仙人掌中就有的点,而一个方点表示一个点双连通分量(环),并且一个方点和它表示的环中所有的点连边。那么我们怎么样构造出圆方树呢?在求点双连通分量的Tarjan算法中稍作修改,因为一个环上的点在栈中的顺序和它们在原仙人掌上的顺序是一样的,所以还可以顺便求出环上的一些信息。要注意的是,如果一个点双中只有一个点,一般不再对这个点建方点。
回到这一题,我们知道树上的最短路用倍增LCA求出即可,转移到仙人掌上,首先要考虑边权的设置。我们自顶向下来看,如果是圆方边,那么边权就是,如果是方圆边,那么边权就是从圆点到方点表示的点双的根(即在圆方树中深度最浅的点)的最短路长度。对于每个环维护环长,再对于每个点维护从它所在的点双(这里指根不为它本身的那个点双)的根到它的最短路是从哪一侧走过来的。接下来我们分类讨论:
1.如果两个点的LCA是圆点,那么它们之间的最短路显然就是路径上的边权和了。
2.如果两个点的LCA是方点,那么它们会往上跳到两个圆点,这两个圆点在同一个环上,因此我们要计算这两个点在环上的最短路。根据从根到这两个点的最短路是不是从同一侧来的分类讨论即可。
整个算法时间复杂度是,可以通过此题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,q,first[10010]={0},tot=0,firsted[20010]={0};
int totpbc,st[10010]={0},low[10010],dfn[10010],tim=0,top=0;
ll dis[10010]={0},last[10010],len[20010],sum[20010][20]={0};
int fa[20010][20]={0},dep[20010]={0};
bool vis[10010]={0},inst[10010]={0},type[10010]={0};
struct edge
{
int v,next,id;
ll w;
}e[100010],ed[200010];
void insert(int a,int b,ll w,int id)
{
e[++tot].v=b;
e[tot].next=first[a];
e[tot].w=w;
e[tot].id=id;
first[a]=tot;
}
void inserted(int a,int b,ll w)
{
ed[++tot].v=b;
ed[tot].next=firsted[a];
ed[tot].w=w;
firsted[a]=tot;
}
void combine(int v,int now)
{
len[++totpbc]=dis[st[top]]+last[st[top]]-dis[v];
for(int i=top;i>=now;i--)
{
ll partA=dis[st[top]]-dis[v];
ll partB=len[totpbc]-dis[st[top]]+dis[v];
if (i>now)
{
inserted(totpbc,st[top],min(partA,partB));
type[st[top]]=partA<=partB;
inst[st[top--]]=0;
}
else inserted(st[top],totpbc,0);
}
}
void tarjan(int v,int laste)
{
vis[v]=1;
st[++top]=v;
inst[v]=1;
dfn[v]=low[v]=++tim;
int now=top;
for(int i=first[v];i;i=e[i].next)
if (e[i].id!=laste)
{
if (!vis[e[i].v])
{
dis[e[i].v]=dis[v]+e[i].w;
tarjan(e[i].v,e[i].id);
if (low[e[i].v]>dfn[v])
{
top--;
inserted(v,e[i].v,e[i].w);
}
else if (low[e[i].v]==dfn[v]) combine(v,now);
low[v]=min(low[v],low[e[i].v]);
}
else if (inst[e[i].v])
{
low[v]=min(low[v],dfn[e[i].v]);
last[v]=e[i].w;
}
}
}
void dfs(int v)
{
for(int i=firsted[v];i;i=ed[i].next)
{
fa[ed[i].v][0]=v;
dep[ed[i].v]=dep[v]+1;
sum[ed[i].v][0]=ed[i].w;
dfs(ed[i].v);
}
}
void solve(int a,int b)
{
ll ans=0;
if (dep[a]<dep[b]) swap(a,b);
for(int i=15;i>=0;i--)
if (dep[fa[a][i]]>=dep[b])
{
ans+=sum[a][i];
a=fa[a][i];
}
if (a==b) {printf("%lld
",ans);return;}
for(int i=15;i>=0;i--)
if (fa[a][i]!=fa[b][i])
{
ans+=sum[a][i]+sum[b][i];
a=fa[a][i],b=fa[b][i];
}
if (fa[a][0]<=n) {printf("%lld
",ans+sum[a][0]+sum[b][0]);return;}
int rt=fa[a][0];
ll A=sum[a][0],B=sum[b][0],mn;
if (type[a]==type[b]) mn=min(abs(A-B),len[rt]-abs(A-B));
else mn=min(A+B,len[rt]-A-B);
printf("%lld
",ans+mn);
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++)
{
int a,b;
ll w;
scanf("%d%d%lld",&a,&b,&w);
insert(a,b,w,i),insert(b,a,w,i);
}
totpbc=n;
tot=0;
tarjan(1,0);
dep[0]=-1;
dfs(1);
for(int i=1;i<=15;i++)
for(int j=1;j<=totpbc;j++)
{
fa[j][i]=fa[fa[j][i-1]][i-1];
sum[j][i]=sum[fa[j][i-1]][i-1]+sum[j][i-1];
}
for(int i=1;i<=q;i++)
{
int a,b;
scanf("%d%d",&a,&b);
solve(a,b);
}
return 0;
}