离线处理,LCA的Tarjan算法 HDU 2586

      首先,什么是离线算法,在线算法呢。在查询问题中,离线算法需要先将所有查询全部输入,

然后统一计算,最后再同意输出。就比如下面介绍的Tarjan求LCA;在线算法就是像前面树上倍

增求LCA一样,预处理(求倍增过程中所需要的量们)所花费的时间相对于整个程序来说较大,

而查询速度相对整个程序来说很快。

     LCA的Tarjan算法的核心是“向上标记”,即不同的点有不同的标记,没访问的点无标记,即为

“0”、访问过但没有回溯的点标记为“1”、访问过并且回溯过的点标记为“2”。然后在dfs序的支配下,

一个即将被回溯的“1”节点与当前树中任意一个回溯过的“2”节点的最近公共祖先LCA即为:“2”点到

根节点的路径上的第一个“1”节点。

     最后找“2”型节点到根节点路径上第一个“1”型节点的操作,可以用并查集优化,即那个“1”型点

就是并查集中对应联通块中的代表元素:find(x)。

PS:补充小知识,并查集的时间复杂度可以近似为o(1)。

例题:HDU2586   http://acm.hdu.edu.cn/showproblem.php?pid=2586

AC代码:

/*离线算法,Tarjan求LCA,复杂度o(n+m)*/
#define ll long long
using namespace std;
const int SIZE=50010;
int ver[2*SIZE],Next[2*SIZE],edge[2*SIZE],head[SIZE];//邻接表
int fat[SIZE],dist[SIZE],v[SIZE],ans[SIZE];
//并查集fat、节点距离根节点的距离、标记数组、两点间距离
vector <int> query[SIZE],query_id[SIZE];
//某次查询的x、y;该查询的 查询标号
int T,n,m,tot;
//多组输入、n、吗、邻接表边数、
void add(int x,int y,int z)
{
    ver[++tot]=y;
    edge[tot]=z;
    Next[tot]=head[x];
    head[x]=tot;
}
void add_query(int x,int y,int id)
//离线:添加查询
{
    query[x].push_back(y);
    query_id[x].push_back(id);
    query[y].push_back(x);
    query_id[y].push_back(id);
}
int find(int x)
{
    if(x==fat[x])return fat[x];
    return fat[x]=find(fat[x]);
}
void unionn(int x,int y)
{
    int fa=find(x),fb=find(y);
    if(fa!=fb)
    {
        fat[fa]=fb;
    }
}
void tarjan(int x)//dfs
{
    v[x]=1;
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(v[y])continue;
        dist[y]=dist[x]+edge[i];
//dfs求树中节点到根节点的距离,PS:不是深度。
        tarjan(y);
        unionn(y,x);
    }
    for(int i=0;i<query[x].size();++i)
    {
        int y=query[x][i],id=query_id[x][i];
        if(v[y]==2)
        {
            int lca=find(y);//x、y的公共祖先
 
            ans[id]=dist[x]+dist[y]-2*dist[lca];//公式求答案
 
            //ans[id]=min(ans[id],dist[x]+dist[y]-2*dist[lca]);书上这么写的,但没必要
        }
    }
    v[x]=2;
}
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>n>>m;
        for(int i=1;i<=n;++i)
        {
            head[i]=0;
            fat[i]=i;//一系列初始化;
            v[i]=0;
            query[i].clear();
            query_id[i].clear();
        }
        tot=0;
        for(int i=1;i<n;++i)
        {
            int x,y,z;
            cin>>x>>y>>z;
            add(x,y,z);
            add(y,x,z);
        }
        for(int i=1;i<=m;++i)//离线
        {
            int x,y;
            cin>>x>>y;
            if(x==y)ans[i]=0;
            else
            {
                add_query(x,y,i);
                ans[i]=(1<<30);
            }
        }
        tarjan(1);//离线
 
        for(int i=1;i<=m;++i)cout<<ans[i]<<endl;
    }
    return 0;
}

 

 

The end;

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值