P5236 【模板】静态仙人掌
这题是个圆方树板子
考虑把原图的点当做圆点,每一个环中间建一个方点
抓住一号点把仙人掌提起来,然后从一个环的顶点向方点连长为0的边,方点向环上其它点连长为这个点到顶点的距离的边
其实就是把一个环上的点到顶点的距离归到这个环的顶点上
这样你自下而上穿过这个环的算得的距离就是在树上的距离
所以询问就是在圆方树上求路径距离,特判一下如果lca是方点,也就是需要从左往右穿过环的距离
这个距离可以记录点在环上按一个方向走的距离和环的总长求解
这篇题解主要讲一些要处理的细节问题
以下内容都是我自己yy的,也不知道对不对,但是它过了你谷的数据,所以如果有大神hack了我麻烦告诉我,毕竟我太菜了/kk
关于重边产生的环的处理
这题数据里没有保证没有重边,但是我看前几篇题解似乎在tarjan里都是传的父节点的标号,然后强制不让tarjan往父节点走
但是这样显然就无法计算到大小为2的环了,当然这道题是求最短距离,还可以把边去重取最短的
我的想法是记录边的编号,然后强制不走过来的边,这样重边也可以被算到了
如何把一个环拿出来
这个地方我之前是从环的顶点出发,暴力枚举边,一个点一个点地找,特地写了个函数,特别麻烦,而且复杂度好像是错的?
我们知道tarjan过程中栈里存的点是按环上顺序排布的,那我们有没有什么办法在按顺序取出点的同时取出边呢?
我的想法是在每个点上维护一个标记表示环上遍历到它的边的长度,这样一个点就对应一条边,按顺序取点就是按顺序取边
想象一下tarjan其实实在dfs树上遍历,走到树边就递归,走到非树边就更新一下low[]
那么我们这个标记也是基于这个思路维护,对于一条边
-
若其为非树边就把
v 的标记更新为l 因为这样的非树边一定对应了一个环,这条边就是把在dfs树上把这个环合上的边
-
若其为树边就在递归完以后把
v 的标记更新为l 这里在递归完以后更新是因为
v 在作为环上的一个点的同时可能是若干个环的顶点,所以它的标记可能被更新过
似乎跟神鱼写的差不多?
这样实现就不需要写一个跟tarjan一样长的连边函数了
下面放一下代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<ctime>
#define ll long long
using namespace std;
int read()
{
int kkk=0,x=1;
char c=getchar();
while((c<'0' || c>'9') && c!='-') c=getchar();
if(c=='-') c=getchar(),x=-1;
while(c>='0' && c<='9') kkk=kkk*10+(c-'0'),c=getchar();
return kkk*x;
}
int n,m,q,head[MAXN*2],tot=1,headn[MAXN*2],totn;
struct node
{
int to,nextn,l;
}a[MAXN*2*2],an[MAXN*2];
void ADD(int from,int to,int l)
{
a[++tot].to=to,a[tot].nextn=head[from],a[tot].l=l;
head[from]=tot;
}
void ADDn(int from,int to,int l)
{
an[++totn].to=to,an[totn].nextn=headn[from],an[totn].l=l;
headn[from]=totn;
}
int size[MAXN],fa_len[MAXN],len[MAXN];
int dfn[MAXN],low[MAXN],tim,z[MAXN],top;
void tarjan(int u,int from)
{
dfn[u]=low[u]=++tim;
z[++top]=u;
for(int i=head[u];i!=0;i=a[i].nextn)
{
int v=a[i].to;
if(!dfn[v])
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
fa_len[v]=a[i].l;
if(low[v]==dfn[u])
{
++n;
int las=0,cnt=1,mem=totn;
size[n]=fa_len[u];
ADDn(u,n,0);
do
{
las=z[top--];
ADDn(n,las,len[las]=size[n]);
size[n]+=fa_len[las];
++cnt;
}while(las!=v);
for(int j=mem+1;j<=totn;++j) an[j].l=min(an[j].l,size[n]-an[j].l);
}
else if(low[v]>dfn[u]) --top,ADDn(u,v,a[i].l);
}
else if(i!=(from^1)) low[u]=min(low[u],dfn[v]),fa_len[v]=a[i].l;
}
}
int deep[MAXN],jump[MAXN][22],dis[MAXN][22];
void format(int u,int fa)
{
deep[u]=deep[fa]+1;
int LOG=log2(deep[u]);
jump[u][0]=fa;
for(int i=1;i<=LOG;++i)
{
jump[u][i]=jump[jump[u][i-1]][i-1];
dis[u][i]=dis[u][i-1]+dis[jump[u][i-1]][i-1];
}
for(int i=headn[u];i!=0;i=an[i].nextn)
{
int v=an[i].to;
if(v==fa) continue;
dis[v][0]=an[i].l;
format(v,u);
}
}
int ans;
int LCA(int &x,int &y)
{
if(deep[x]<deep[y]) swap(x,y);
int C=deep[x]-deep[y],LOG=log2(C);
for(int i=0;i<=LOG;++i) if(C&(1<<i)) ans+=dis[x][i],x=jump[x][i];
if(x==y) return x;
LOG=log2(deep[x]);
for(int i=LOG;i>=0;--i) if(jump[x][i]!=jump[y][i])
{
ans+=dis[x][i]+dis[y][i];
x=jump[x][i],y=jump[y][i];
}
return jump[x][0];
}
int main()
{
n=read(),m=read(),q=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read(),l=read();
ADD(u,v,l);
ADD(v,u,l);
}
int tmp=n;
for(int i=1;i<=tmp;++i) if(!dfn[i]) tarjan(i,0);
format(1,0);
while(q--)
{
ans=0;
int x=read(),y=read(),lca=LCA(x,y);
if(lca>tmp) printf("%d\n",ans+min(abs(len[x]-len[y]),size[lca]-abs(len[x]-len[y])));
else if(x==y) printf("%d\n",ans);
else printf("%d\n",ans+dis[x][0]+dis[y][0]);
}
return 0;
}