bzoj 2125: 最短路

仙人掌 之 最短路 

 

 

/**************************************************************
    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;
}

 

转载于:https://www.cnblogs.com/lxy8584099/p/10170084.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值