商务旅行

code1036: 商务旅行
时间限制: 1 s
空间限制: 128000 KB
题目描述 Description
某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间。

假设有N个城镇,首都编号为1,商人从首都出发,其他各城镇之间都有道路连接,任意两个城镇之间如果有直连道路,在他们之间行驶需要花费单位时间。该国公路网络发达,从首都出发能到达任意一个城镇,并且公路网络不会存在环。

你的任务是帮助该商人计算一下他的最短旅行时间。

输入描述 Input Description
输入文件中的第一行有一个整数N,1<=n<=30 000,为城镇的数目。下面N-1行,每行由两个整数a 和b (1<=a, b<=n; a<>b)组成,表示城镇a和城镇b有公路连接。在第N+1行为一个整数M,下面的M行,每行有该商人需要顺次经过的各城镇编号。

输出描述 Output Description
在输出文件中输出该商人旅行的最短时间。

样例输入 Sample Input
5
1 2
1 5
3 5
4 5
4
1
3
2
5
样例输出 Sample Output
7

题解:很明显就是求某两点的最短路径长度的和嘛,那就是求lca咯。
我先用了链式前向星存储图,接下来我用两种方法解决此题。

第一种方法:
离线tarjan+并查集

下面详细介绍一下Tarjan算法的基本思路(摘抄自某资料):
      1.任选一个点为根节点,从根节点开始。
      2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
      3.若是v还有子节点,返回2,否则下一步。
      4.合并v到u上。
      5.寻找与当前点u有询问关系的点v。
      6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
    遍历的话需要用到dfs来遍历(我相信来看的人都懂吧…),至于合并,最优化的方式就是利用并查集来合并两个节点。

上代码:

//离线tarjan+并查集 
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hh
{
    int next,to,bh;
}a[1000000],qa[50001];
int n,m,h[50001],qh[50001],x,y,aa=0,qaa=0,sd[50001],ans=0,fa[50001];
bool b[500001],bb[50001];
int find(int x)
{
    if(x==fa[x]) return fa[x];//并查集的操作,类似于找某棵树的根节点 
    return fa[x]=find(fa[x]);
}
void tarjan(int u)
{
    b[u]=1;//b[i]保存i点有没有被访问过,b[i]==1就是访问过。 
    fa[u]=u;
    for(int i=h[u];i;i=a[i].next)
    if(!b[a[i].to])//找子节点 
    {
        sd[a[i].to]=sd[u]+1;//深度加上一 
        tarjan(a[i].to);
        fa[a[i].to]=u;
    }
    for(int i=qh[u];i;i=qa[i].next)
    if(b[qa[i].to]&&!bb[qa[i].bh])
    {
        ans+=sd[qa[i].to]+sd[u]-2*sd[find(qa[i].to)];//数学计算答案 
        bb[qa[i].bh]=1;//bb[i]==1表示第i条边被访问过 
    }
}
void add1(int x,int y)
{
    aa++;
    a[aa].to=y;
    a[aa].next=h[x];
    h[x]=aa;
}
void add2(int x,int y,int z)
{
    qaa++;
    qa[qaa].to=y;
    qa[qaa].bh=z;
    qa[qaa].next=qh[x];
    qh[x]=qaa;
}
int main()
{
    memset(h,0,sizeof(h));
    memset(qh,0,sizeof(qh));
    memset(b,0,sizeof(b));
    memset(bb,0,sizeof(bb));
    cin>>n;
    for(int i=1;i<n;i++)
    {
        cin>>x>>y;//无向图,双向加边 
        add1(x,y);
        add1(y,x);
    }
    cin>>m;
    cin>>x;
    for(int i=2;i<=m;i++)
    {
        cin>>y;//保存有关于每个点的每个询问 
        add2(x,y,i-1);
        add2(y,x,i-1);
        x=y;
    }
    sd[1]=0;//sd[i]表示第i个点的深度 
    tarjan(1);//tarjan算法 
    cout<<ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值