hdu 2586 How far away ? 最近公共祖先lca 在线算法(倍增法)/离线算法(Tarjan算法)

2 篇文章 0 订阅
#pragma comment(linker, "/STACK:1024000000,1024000000")  
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node{
    int to,w;
    node(int a=0,int b=0){to=a;w=b;}
};
vector<node>e[maxn];
int f[maxn],dis[maxn],deep[maxn],p[maxn][20],n;
void dfs(int u,int pre,int t)
{
    int i,num;
    deep[u]=t;//深度
    f[u]=pre;//父节点
    num=e[u].size();
    for(i=0;i<num;i++)
    {
        int v=e[u][i].to;
        if(v!=pre)
        {
            dis[v]=dis[u]+e[u][i].w;//距离跟的距离
            dfs(v,u,t+1);
        }
    }
}
void init()
{
    //p[i][j]表示i结点的第2^j祖先
    int i,j;
    for(j=0;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
        p[i][j]=-1;
    for(i=1;i<=n;i++)p[i][0]=f[i];
    for(j=1;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
        if(p[i][j-1]!=-1)
        p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//最近公共祖先
{
    int i,j;
    if(deep[a]<deep[b])swap(a,b);
    for(i=0;(1<<i)<=deep[a];i++);
    i--;
    //使a,b两点的深度相同
    for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
        a=p[a][j];
    if(a==b)return a;
    //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
    for(j=i;j>=0;j--)
    {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
            a=p[a][j];
            b=p[b][j];
        }
    }
    return f[a];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int m,i,a,b,c,ans;
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)e[i].clear();
        for(i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            e[a].push_back(node(b,c));
            e[b].push_back(node(a,c));
        }
        dis[1]=0;
        dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离
        init();     //初始各个点的2^j祖先是谁    
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            ans=dis[a]+dis[b]-2*dis[lca(a,b)];
            printf("%d\n",ans);
        }
    }
    return 0;
}
/*
    最近公共祖先lca,在线算法/倍增法,模板题。套别人模板自己敲了遍,现在还要回顾下邻接表,哎。。
    用vector,发现爆栈了,汗一个。。
    用#pragma comment(linker, "/STACK:1024000000,1024000000") 开个把栈开大点吧。。hdu可以,别的地方就不清楚了
*/


下面是用邻接表存图,经证明栈内存不够主要应该是在线运算太耗了的缘故吧。。


#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node{
    int to,w,next;
}e[maxn*2];
int f[maxn],dis[maxn],deep[maxn],p[maxn][20],n,tt,head[maxn];
void add(int a,int b,int c)
{
    e[tt].to=b;
    e[tt].w=c;
    e[tt].next=head[a];
    head[a]=tt++;
}
void dfs(int u,int pre,int t)
{
    int i,num;
    deep[u]=t;//深度
    f[u]=pre;//父节点
    for(i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=pre)
        {
            dis[v]=dis[u]+e[i].w;//距离跟的距离
            dfs(v,u,t+1);
        }
    }
}
void init()
{
    //p[i][j]表示i结点的第2^j祖先
    int i,j;
    for(j=0;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
        p[i][j]=-1;
    for(i=1;i<=n;i++)p[i][0]=f[i];
    for(j=1;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
        if(p[i][j-1]!=-1)
        p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//最近公共祖先
{
    int i,j;
    if(deep[a]<deep[b])swap(a,b);
    for(i=0;(1<<i)<=deep[a];i++);
    i--;
    //使a,b两点的深度相同
    for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
        a=p[a][j];
    if(a==b)return a;
    //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
    for(j=i;j>=0;j--)
    {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
            a=p[a][j];
            b=p[b][j];
        }
    }
    return f[a];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int m,i,a,b,c,ans;
        scanf("%d%d",&n,&m);
        memset(head,-1,sizeof(head));
        tt=0;
        for(i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        dis[1]=0;
        dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离
        init();     //初始各个点的2^j祖先是谁
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            ans=dis[a]+dis[b]-2*dis[lca(a,b)];
            printf("%d\n",ans);
        }
    }
    return 0;
}


下面是离线算法,我想应该不会栈内存不够啊,为什么还是爆。。

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node{
    int to,w;
    node(int a=0,int b=0){to=a;w=b;}
};
int f[maxn],dis[maxn],n,ans[maxn],vis[maxn];
//f[i]并查集所用,记录前继结点
//dis[i]记录个点到跟结点的距离
//ans记录m个询问的答案
//vis标记查询过了的点
vector<node>e[maxn];//记录树
vector<node>q[maxn];//记录所求最短距离的两点
int find(int x)
{
    if(x!=f[x])f[x]=find(f[x]);
    return f[x];
}
void lca(int u)
{
    int i,j,k,v,c;
    for(i=0;i<e[u].size();i++)
    {
        v=e[u][i].to;
        if(vis[v])continue;
        vis[v]=1;
        dis[v]=dis[u]+e[u][i].w;
        lca(v);//深度优先搜索
        f[v]=u;
        for(j=0;j<q[v].size();j++)
        {
            c=q[v][j].to;
            //如果所求两点中的对应点已知,则必定在同一子树上,由于并查集只记录所在子树。所以find(c),就是最近公共祖先
            if(vis[c]&&ans[q[v][j].w]==-1)
            {
                if(v==c)ans[q[v][j].w]=0;//自成环
                else ans[q[v][j].w]=dis[v]+dis[c]-2*dis[find(c)];
            }
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int i,j,k,a,b,c,m,aa,bb;
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
        {
            e[i].clear();
            q[i].clear();
            ans[i]=-1;
            f[i]=i;
            vis[i]=0;
        }
        for(i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            e[a].push_back(node(b,c));
            e[b].push_back(node(a,c));
        }
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            q[a].push_back(node(b,i));
            q[b].push_back(node(a,i));
        }
        vis[1]=1;
        dis[1]=0;
        lca(1);
        for(i=1;i<=m;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}
/*
    最近公共祖先lca 离线算法/Tarjan算法
    
    方法举例说明:
                1
               / \
              2   3
             / \
            4   5
           /   /
          7   8
         / 
        9  
    查询(4,9):到4时,由于vis[9]=0,所以继续;到9后,最近公共祖先就是f[4]=4(回溯的时候记录父节点);
    查询(9,8):深度优先搜索,所以到8时才询问;要到8必须回溯到2,在进到8这个子树,所以以记录f[9]=7;f[7]=4;f[4]=2;f[2]=2;所以find(2)=2;
    查询(8,3):跟上条相似,必须回溯1才能到3,而find(8)=1就是最近公共祖先;
    我们可以发现,对于查询的两点,都要在先查询到的点开始,回溯到最近公共祖先,才查询相对应的点。这也就是离线算法的思路了。。这是我自己理解的。。
    
    下面是专业说明,反正我是没看懂。。
    
    利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。
    Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,
    再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。
    其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。
    之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,
    同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,
    且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,
    而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。
*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值