世界真的很大
这是今天的第三题,真的是
当时我反应过来是仙人掌的时候,大脑就闪过“沙漠中的顽强植物”
使用的方法很厉害,虽然可能比较超纲,但是的却算是学到了
思路理清用不了多久但是调试的时候由于对于点双连通分量的不熟悉所以说用了比较久的时间
今天一天怎么感觉又快莫名其妙的过去了啊
NOIP2017
看题先:
给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径。
输入的第一行包含三个整数,分别表示N和M和Q 下接M行,每行三个整数v,u,w表示一条无向边v-u,长度为w 最后Q行,每行两个整数v,u表示一组询问
输出Q行,每行一个整数表示询问的答案
首先对于一张图来讲,肯定是不存在n^3以下的多原最短路的算法的,不然你让floyd怎么活
但是这道题偏偏就需要一个这样的算法,这当然是不可能的
于是乎只能考虑题目的性质比较特殊了。
每一条边只处于一个环里面。
这样的图叫做仙人掌图,至于为什么,大概是由于画出来比较像仙人掌吧
观察发现这样的图其实就是一棵树上有一些环而已
如果没有这些环只是一棵树的话是很简单的,只需要记录每个点到根的距离询问时求lca加加减减就行了
那么对于这个类似树的仙人掌图我们也进行同样的操作,记录每个点到根(设为1)的距离,当然是记录最短距离了,这个可以用SPFA单源最短路预处理出来
发现只要这样记录之后,加加减减好像的确是可以的
那么问题就变成了如何求lca,因为这是一张无向图对吧,尽管长的和树差不多但是毕竟不一样,肯定不能处理Lca什么的
考试时就在这里卡住了
这个就是仙人掌图才能做到的操作了
对于树上的每一个环,我们改变他的构造
取环上深度(即离1最近)最浅的点来”代表“这个环
环上其余所有点向这个点连边,这样就把原图转化成一棵树了
注意的是这棵树的实际含义近乎于没有,单单是为了访问lca而存在的构型转化模式
其避免了lca无法处理环的情况,如果两个点在访问lca的时候,一个环只是经过了u到lca或者v到lca的路径上的话完全不影响对吧
如果正好在lca的位置的话就假设是环上最上面的一个点是lca
这一步就是这么一个模型转化而已
所以说这个新树并不是什么东西,上面的边也没有边权,加加减减还是按照之前的最短路来做
然后就是为什么只有,或者说仙人掌图可以这样做,而一般的图不行
原因就在于仙人掌图的所有环都是简单环,不会有复环的情况出现
这样对于每一个环内的点当指定了根之后就有唯一的”深度最浅“的点来连边进而转化成一棵树了
这一步,由于这些环都是点双连通(一条边不在两个环里),所以可以用点双连通的tajan求到
值得注意的是点双连通分量由于一个点可能处于多个点双连通分量里,所以tarjan的时候是将边压进栈里面
而压边的时候一定要注意方向,同一条边不要正反两次都压进去。这是因为搜索的顺序不同导致这两条一样的边在栈里面的位置可能会被割点的连边分开,这样就无法再一个点双连通里面弹出,会遗留在下一个点双连通分量的弹栈里面
但是我们发现,对于每一个lca不在一个环里面的u,v这样做是没有问题的,但是当lca处于一个环里面的时候就不对头了
因为在一个环里面,就有两条路可以走(环的上半部和下半部),而如果我们这样选,相当于就默认走上半部,但完全有可能下半部更优
所以在这种情况的时候,我们就需要比较,记录一下每个环的长度
然后记录u和v各自与这个环第一次接触的点,比较这两点间的距离,比较环的长度减去这两点间的距离,取小
那么问题在于怎么取得两点间的距离
方法是在DFStarjan的时候记录到每个点的dfs路径的长度,记为rdo[i](redo),由于环上是DFS的时候会连续访问的一段,所以直接相减就行了
当然我们不知道dfs的时候u的接触点在前还是v的在前,所以取一个abs
这道题大概就讲完了
值得注意的地方还有,在tarjan求点双连通重新构图的时候有可能两个点之间多次建边,建重边,虽然理论复杂度是O(1)的但是多次建边还是伤不起。
所以先用一个数组保存下来,最后再建边,每条边就只建一次了
感觉树这个东西是好神奇的
图论的时候如果有特殊的题面应该往树上想
像这道题就是如此,并不是什么单一的算法题,而是在解题过程中,遇到什么问题需要解决才去寻找解决的适合算法
真正把算法当成工具来用,这样的题往往不简单,但却能一点点体现算法的实质~~
完整代码:
#include
#include
#include
#include
#include
using namespace std;
typedef long long dnt;
const dnt INF=0x3f3f3f3f;
struct edge
{
int u,v,last;
dnt w;
}ed[200010],ad[200010];
queue <int> state;
stack <int> stk;
int n,m,Q,num=1,mum=0,cnt=0,idx=0;
int head[100010],hed[100010],low[100010],fa[100010],dfn[100010];
int dep[100010],place[100010],se[100010],anc[100010][20];
dnt dis[100010],rdo[100010],len[100010];
void add(int u,int v,int w)
{
num++;
ed[num].v=v;
ed[num].u=u;
ed[num].w=w;
ed[num].last=head[u];
head[u]=num;
}
void ade(int u,int v)
{
mum++;
ad[mum].v=v;
ad[mum].last=hed[u];
hed[u]=mum;
}
void SPFA(int S)
{
for(int i=1;i<=n;i++) dis[i]=10000000000000LL;
state.push(S),dis[S]=0,se[S]=1;
while(!state.empty())
{
int u=state.front();
state.pop(),se[u]=0;
for(int i=head[u];i;i=ed[i].last)
{
int v=ed[i].v;
if(dis[v]>dis[u]+ed[i].w)
{
dis[v]=dis[u]+ed[i].w;
if(!se[v]) se[v]=1,state.push(v);
}
}
}
}
void setle(int S,int T)
{
cnt++;
int st=-1,nd=-1;
while(st!=S && nd!=T)
{
int i=stk.top();
int u=ed[i].u,v=ed[i].v;
st=u,nd=v;
if(u!=S) place[u]=cnt,anc[u][0]=S;
if(v!=S) place[v]=cnt,anc[v][0]=S;
len[cnt]+=ed[i].w;
stk.pop();
}
}
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
for(int i=head[u];i;i=ed[i].last)
{
int v=ed[i].v;
if(i==(fa[u]^1)) continue ;
if(!dfn[v])
{
stk.push(i);
rdo[v]=rdo[u]+ed[i].w,fa[v]=i;
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) setle(u,v);
}
else if(low[u]>dfn[v])low[u]=dfn[v],stk.push(i);
}
}
void dfs(int u,int f)
{
anc[u][0]=f;
for(int i=1;i<=18;i++)
anc[u][i]=anc[anc[u][i-1]][i-1];
for(int i=hed[u];i;i=ad[i].last)
{
int v=ad[i].v;
if(v==f) continue ;
dep[v]=dep[u]+1;
dfs(v,u);
}
}
int lca(int u,int v,int &a,int &b)
{
if(dep[u]for(int i=18;i>=0;i--)
if(dep[anc[u][i]]>=dep[v])
u=anc[u][i];
if(u==v) return u;
for(int i=18;i>=0;i--)
if(anc[u][i]!=anc[v][i])
u=anc[u][i],v=anc[v][i];
a=u,b=v;
return anc[u][0];
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
SPFA(1);
tarjan(1);
for(int i=2;i<=n;i++)
ade(anc[i][0],i);
dfs(1,1);
while(Q--)
{
int u,v,a=0,b=0;
scanf("%d%d",&u,&v);
int f=lca(u,v,a,b);
if(place[a]!=0 && a && place[a]==place[b])
{
dnt rt=dis[u]-dis[a]+dis[v]-dis[b];
dnt lt=min(len[place[a]]-abs(rdo[a]-rdo[b]),abs(rdo[a]-rdo[b]));
printf("%lld\n",rt+lt);
}
else printf("%lld\n",dis[u]+dis[v]-2*dis[f]);
}
return 0;
}
/* Whoso pulleth out this sword from this stone and anvil is duly born King of all England */
嗯,就是这样