图论——最先公共祖先(LCA)模板

祖孙询问(在线做法)

给定n个节点,m条边,输入a,b代表a,b间有个无向边,b=-1时为根,m组询问a是b的祖先时输出1,反之输出2,互不为输出0.

const int N = 40010, M = N * 2;//点数、边数最大值
int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][16];//depth代表深度  fa[i][k]代表节点i向上跳2^k次的节点编号
int q[N];
void add(int a, int b)
{  e[idx] = b;ne[idx] = h[a];h[a] = idx ++;}
void bfs(int root)//宽搜不容易因为递归层数过多爆栈
{
    memset(depth,0x3f,sizeof depth);
    depth[0] = 0,depth[root] = 1;  queue<int> q;  q.push(root);
    while(q.size())
    {int t = q.front(); q.pop();
        for(int i=h[t];i!=-1;i=ne[i])
        {   int j = e[i];
            if(depth[j]>depth[t]+1)//说明j还没被搜索过
            {
                depth[j] = depth[t]+1;q.push(j);fa[j][0] = t;//把第depth[j]层的j加进队列j往上跳2^0=1步后就是父节点t
                for(int k=1;k<=15;k++)//从小到大往上跳递推
                  fa[j][k] = fa[fa[j][k-1]][k-1];
            }}}}
int lca(int a, int b)//返回a、b公共祖先
{
    // 为方便处理 当a在b上面时 把a b 互换  
    if (depth[a] < depth[b]) swap(a, b);    //把深度更深的a往上跳到b
    for (int k = 15; k >= 0; k -- )  //当a跳完2^k依然在b下面 我们就一直跳
        if (depth[fa[a][k]] >= depth[b])  a = fa[a][k];
    if (a == b) return a;    //a,b同层但不同节点
    for (int k = 15; k >= 0; k -- )
        if (fa[a][k] != fa[b][k])
        {  a = fa[a][k];  b = fa[b][k];} 
    return fa[a][0]; //lca(a,b) = 再往上跳1步即可
}
int getdis(int x, int y)
{
    return depth[x] + depth[y] - 2 * depth[lca(x, y)];
}
int main()
{
    cin >> n;int root = 0; memset(h, -1, sizeof h);
    for (int i = 0; i < n; i ++ )
       { int a, b;cin >> a >> b;if (b == -1) root = a;  else add(a, b), add(b, a);}
    bfs(root); scanf("%d", &m);
    while (m -- )
    {
        int a, b;  cin >> a >> b;
        int p = lca(a, b)  if (p == a) cout << "1" << endl;  else if (p == b) cout << "2" << endl; else cout << "0" << endl;
    }
}


离线LCA O(N+M)

给出 n 个点的一棵树,多次询问两点之间的最短距离。两个节点到根的距离-两倍根节点到最先公共祖先的距离。

const int N = 10010, M = N * 2;
int n, m, h[N], e[M], w[M], ne[M], idx,dist[N],p[N],res[M],st[N];
//dist[i]为i节点到根节点的距离  p[i]并查集 res[i]第i次询问的答案,st[i]第i个点是否访问
vector<PII> query[N];//把询问存下来   
// query[i][first][second] first存查询距离i的另外一个点j,second存查询编号idx
void add(int a,int b,int c)
 e[idx] = b; ne[idx] = h[a];w[idx] = c;h[a] = idx++;
int find(int x)
if(p[x]!=x)p[x] = find(p[x]); return p[x];
void dfs(int u,int fa)
  for(int i=h[u];~i;i=ne[i])
 {int j = e[i];if(j==fa) continue;dist[j] = dist[u]+w[i]; dfs(j,u);}

void tarjan(int u)
{  //2号点:代表已经访问并结束回溯1号点:代表正在访问0号点:代表还没有访问过其中所有2号点和正在搜索的1号点路径中已经通过并查集合并成一个集合
    st[u]=1;//当前路径点标记为1    u这条路上的根节点的左下的点用并查集合并到根节点
    for(int i = h[u];~i;i=ne[i])
    {  int j = e[i];  if(!st[j]){ tarjan(j);p[j] = u;}//从左下回溯后把左下的点合并到根节点}
    // 对于当前点u 搜索所有和u
    for(auto item:query[u])
    {
        int y = item.first,id = item.second;
        if(st[y]==2)//如果查询的这个点已经是左下的点(已经搜索过且回溯过,标记为2)
        { int anc = find(y);//y的根节点 // x到y的距离 = d[x]+d[y] - 2*d[lca] 
            res[id] = dist[u]+dist[y] - dist[anc]*2;//第idx次查询的结果 res[idx]
        }
    }    st[u] = 2;   //点u已经搜索完且要回溯了 就把st[u]标记为2 
}
int main()
{
    cin >> n >> m;  memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++)
        int a,b,c; cin >> a >> b >> c;add(a,b,c),add(b,a,c);
    // 存下询问
    for(int i=0;i<m;i++)
    {
        int a,b;   cin >> a >> b;
        if(a!=b) query[a].push_back({b,i}),query[b].push_back({a,i});
    }
    for(int i=1;i<=n;i++)p[i] = i;
    dfs(1,-1); tarjan(1); for(int i=0;i<m;i++)cout << res[i] << '\n';//把每次询问的答案输出

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值