仙人掌 之 最短路
/************************************************************** Problem: 2125 User: lxy8584099 Language: C++ Result: Accepted Time:380 ms Memory:5392 kb ****************************************************************/ /* 圆方树 之 最短路问题 http://immortalco.blog.uoj.ac/blog/1955 圆点 仙人掌原有的点 方点 新建的点 对于一个环 栈中最深的节点做为根 新建一个方点 P 环上的路全删 所有点连接到方点P上 (菊花图 这样缩下去就成了一棵树 圆点->方点的权值为0 方点->圆点的权值为环中次圆点到根节点的最短路 圆点->圆点就是原仙人掌的权值 不存在方点->方点。(特性 类比树上最短路 寻找x,y的 LCA 同时把路径也算进去 若LCA 是圆点 则仙人掌的最短路就是圆方树的最短路 否则 分别求出x到环上的点 u y到环上的点v 求u,v 的最短路 这里可以用前缀和解决 照着抄 终于抄对了。。。 */ #include<cstdio> #include<cstring> #define ll long long #define min(a,b) ((a>b)?(b):(a)) using namespace std; const int M=20050; struct pp {int v,nxt,d;} e[M*2],E[M*2]; int n,m,Q,tot=1,ToT=1,N,kok,head[M],Head[M]; int dfn[M],low[M],fa[M],b[M],s[M],id[M]; int f[M][32],dep[M],lg[M],A,B; ll dis[M]; /* e用于仙人掌 E用于圆方树 含大写的用于圆方树 fa记录父亲 b记录到这个点走路的权值 s记录前缀和 id记录环中每个节点的先后序列 f,dep,lg 常规倍增所用 */ void Add(int u,int v,int d) { e[++tot].nxt=head[u];head[u]=tot;e[tot].v=v;e[tot].d=d; e[++tot].nxt=head[v];head[v]=tot;e[tot].v=u;e[tot].d=d; } void Insert(int u,int v,int d) { E[++ToT].nxt=Head[u];Head[u]=ToT;E[ToT].v=v;E[ToT].d=d; E[++ToT].nxt=Head[v];Head[v]=ToT;E[ToT].v=u;E[ToT].d=d; } // 树也要建双边!!! void Init() { scanf("%d%d%d",&n,&m,&Q); for(int i=1;i<=n;i++) lg[i]=lg[i-1]+((1<<(lg[i-1]+1))==i); for(int i=1,u,v,d;i<=m;i++) { scanf("%d%d%d",&u,&v,&d); Add(u,v,d); } } void Work(int u,int v,int d) // 处理环 换种都连到新节点 N { // printf("______________________\n"); N++; int pre=d,ID=0; for(int i=v;i!=fa[u];i=fa[i]) { // printf("%d ",i); s[i]=pre; pre+=b[i]; id[i]=ID++; } s[N]=s[u];s[u]=0; // 把整个环的路劲放在S[n]里面 因为s[u]是第一个 为 0 // printf("\n____________________\n"); for(int i=v;i!=fa[u];i=fa[i]) { Insert(N,i,min(s[i],s[N]-s[i])); // printf(" u:%d v:%d\n",N,i); } } void Tarjan(int u,int la) { dfn[u]=low[u]=++kok; for(int j=head[u];j;j=e[j].nxt) if(j!=la) { int v=e[j].v; if(!dfn[v]) { fa[v]=u; b[v]=e[j].d; Tarjan(v,j^1); low[u]=min(low[u],low[v]); } else low[u]=min(low[u],dfn[v]); if(low[v]>dfn[u]) // 不存在环 { // printf(" u:%d v:%d\n",u,v); Insert(u,v,e[j].d); } } for(int j=head[u];j;j=e[j].nxt) // 处理如果存在环的情况 { int v=e[j].v; if(fa[v]!=u&&dfn[v]>dfn[u]) Work(u,v,e[j].d); // 存在环至少为3元 所以 fa[v]!=u // dfnv>dfnu是控制不要走到父节点 } } void Dfs(int u,int la) // 常规的倍增 { // printf(" %d\n",u); for(int i=1;(1<<i)<=dep[u];i++) f[u][i]=f[f[u][i-1]][i-1]; for(int j=Head[u];j;j=E[j].nxt) if(j!=la) { int v=E[j].v,d=E[j].d; dep[v]=dep[u]+1;f[v][0]=u; dis[v]=dis[u]+d; Dfs(v,j^1); } } int Lca(int x,int y) // 常规的倍增求LCA { if(dep[x]<dep[y]) x^=y^=x^=y; while(dep[x]>dep[y]) x=f[x][lg[dep[x]-dep[y]-1]]; if(x==y) return x; for(int k=lg[dep[x]];k>=0;k--) if(f[x][k]!=f[y][k]) x=f[x][k],y=f[y][k]; A=x,B=y; return f[x][0]; // 此处记录一下各自走到了哪一点 如果Lca是方点就要用到 } void Solve() { N=n;Tarjan(1,0);Dfs(1,0); // printf("ToT:%d\n",ToT); // for(int i=1;i<=N;i++) printf("%d : %d\n",i,dis[i]); // return ; while(Q--) { int u,v,k; scanf("%d%d",&u,&v); k=Lca(u,v); if(k<=n) printf("%lld\n",dis[u]+dis[v]-dis[k]*2); // 树上差分 else // 该点是方点 就要在环上找最短路 { ll ans=dis[u]+dis[v]-dis[A]-dis[B]; if(id[A]>id[B]) A^=B^=A^=B; ans+=min(s[A]+s[k]-s[B],s[B]-s[A]); printf("%lld\n",ans); } } } int main() { Init(); Solve(); return 0; }