首页 > 其他分享 >最近公共祖先

最近公共祖先

时间:2023-04-30 22:56:34浏览次数:34  
标签:fa 祖先 LCA int depth 最近 公共 quad 节点

倍增求LCA

① 初始化: 通过 bfs 初始化两个数组 depth[] , fa[]

\(\quad\) \(\quad\) depth[n] : 表示深度(到根节点的距离加1)

\(\quad\) \(\quad\) fa[i][j] : 表示从 i 开始, 向上走 \(2^j\) 步所能到的节点编号 (\(0 \leqslant j \leqslant log_2n\))

\(\quad\) \(\quad\) \(\quad\) \(\quad\) \(\quad\) \(\quad\) fa[i,0] = i 的父节点
\(\quad\) \(\quad\) \(\quad\) \(\quad\) \(\quad\) \(\quad\) fa[i,j] = fa[fa[i,j-1],j-1]

\(\quad\) \(\quad\) 哨兵: 如果从 i 开始跳 \(2^j\) 步会跳过根节点, 那么 fa[i,j] = 0 , depth[0] = 0

② 查询

\(\quad\) \(\quad\) [1] 先将两个点跳到同一层

\(\quad\) \(\quad\) [2] 让两个点同时往上跳, 一直跳到它们的最近公共祖先的下一层


//倍增法求最近公共祖先算法模板
int h[N],e[M],ne[M],idx;
int depth[N],fa[N][size];	//size取logn(n为节点数量)
int q[N];

//宽搜预处理depth和fa数组
void bfs (int root)
{
    memset(depth,0x3f,sizeof depth);
    depth[0]=0,depth[root]=1;
    int hh=0,tt=0;
    q[0]=root;
    
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(depth[j]>depth[t]+1)
            {
                depth[j]>depth[t]+1;
                q[++tt]=j;
                fa[j][0]=t;
                for(int k=1;k<=size;k++)
                    fa[j][k]=fa[fa[j][k-1]][k-1];
            }
        }
    }
}

//查询节点a,b的最近公共祖先
int lca (int a,int b)
{
    if(depth[a]<depth[b])swap(a,b);
    for(int k=size;k>=0;k--)
        if(depth[fa[a][k]]>=depth[b])
            a=fa[a][k];
    if(a==b)return a;
    
    for(int k=size;k>=0;k--)
        if(fa[a][k]!=fa[b][k])
        {
            a=fa[a][k];
            b=fa[b][k];
        }
    return fa[a][0];
}



Tarjan——离线求LCA

在深度优先遍历时, 将所有点分为三大类:

(1) 已经遍历过且回溯过的点, 标记为2

(2) 正在搜索的分支上的点, 标记为1

(3) 还未搜索到的点, 标记为0

如果当前正在搜索上图中的点 x , 则找一下所有和这个点 x 相关的询问, 假设询问 xy 的最近公共祖先, 如果点 y 在绿色圈中, 则可以得到 xyLCA 是图中对应的红色实心点; 如果 y 还没被搜索到, 则直接忽略即可, 后面的遍历过程中遍历到点 y 时, 就会处理这个询问

因此我们可以使用并查集将所有标记为 2 的点合并到它们对应的红色实心节点中, 当我们需要查看 x 和某个绿色圈中的点 yLCA 时, 只需要查看一下点 y 合并到了哪个点中即可


//Tarjan离线求LCA算法模板
int n,m;	//n为节点数,m为询问数量
int h[N],e[M],ne[M],w[M],idx;
int p[N];
int ans[M];		//ans[i]记录第i个询问的LCA的值
int st[N];		//标记每个节点的状态0/1/2

vector<pair<int,int>> query[M];	//记录询问,first存查询的另外一个点,second存查询编号

//并查集操作
int find (int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}

//tarjan操作求每个询问
void tarjan (int u)
{
    st[u]=1;	//正在搜索分支上的点,标记为1
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!st[j])
        {
            tarjan(j);
            p[j]=u;
        }
    }
    
    for(auto item: query[u])	//遍历所有和这个点有关的询问
    {
        int y=item.first,id=item.second;
        if(st[y]==2)ans[id]=find(y);
    }
    
    st[u]=2;	//已经遍历过且回溯过的点,标记为2
}

//主函数,记录所有询问到query[][]中
int main()
{
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        query[a].push_back({b,i});
        query[b].push_back({a,i});
    }
    
    for(int i=1;i<=n;i++)p[i]=i;
    tarjan(1);	//随便选其中一个节点作为根节点均可
    
    for(int i=0;i<m;i++)cout<<ans[i]<<' ';
    return 0;
}



树上差分

点权差分

f[i] 表示点 i 的点权, w[i] 表示 i 及其子树权值之和

若给 [x,y] 两点之间路径上所有点的权值 +d , 点差分因为需要加上 LCA 点, 但左右起点到终点时会加两次, 所有在此点及其父节点各减一份 f[x] += d , f[y] += d , f[lca(x,y)] -= d , f[fa[lca(x,y)]] -= d

边权差分

f[i] 表示点 i 到其父节点的边权, w[i] 表示 i 的子树权值之和

若给 [x,y] 两点之间路径上的所有边的权值 +d , 边差分由于边数等于点数减一, 不计 LCA 点, 所以在此点减两份 f[x] += d , f[y] += d , f[lca(x,y)] -= d*2

最后 DFS 由底向上统计更新即可


//树上遍历dfs(由下往上)
void dfs (int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==fa)continue;
        dfs(j,u);
        f[u]+=f[j];
    }
}


标签:fa,祖先,LCA,int,depth,最近,公共,quad,节点
From: https://www.cnblogs.com/evilboy/p/17365908.html

相关文章

  • 「学习笔记」tarjan求最近公共祖先
    Tarjan算法是一种离线算法,需要使用并查集记录某个结点的祖先结点。并没有传说中的那么快。过程将询问都记录下来,将它们建成正向边和反向边。在dfs的过程中,给走过的节点打上标记,同时维护并查集,这里利用了回溯的思想,如果\(u\)节点的这棵子树没搜完,那么fa[u]=u;,搜完后......
  • 两个链表的第一个公共结点
    使用空间存储节点的解法classSolution{public:set<ListNode*>s;ListNode*findFirstCommonNode(ListNode*headA,ListNode*headB){for(autoi=headA;i;i=i->next)s.insert(i);for(autoi=headB;i;i=i->next)......
  • 获取最近7天的日期List列表
    importcn.hutool.core.date.DateField;importcn.hutool.core.date.DateUtil;/***获取最近7天的日期*@returnList*/privateList<LastWeekDateVo>generate7DateList(){List<LastWeekDateVo>list=newArrayList<>();DatecurrentDate=newDate......
  • 【LeetCode动态规划#14】子序列系列题(最长递增子序列、最长连续递增序列、最长重复子
    最长递增子序列力扣题目链接(opensnewwindow)给你一个整数数组nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]是数组[0,3,1,6,2,2,7]的子序列。示例1:输入:nums=[10,9,2,5,3,7......
  • 谈谈最近职场的一些思考
    今日鸡汤人生难得几回搏,此时不博待何时!告别校园,来到公司已经4个多月了,这四个月也真是快啊,真的是转眼之间,这日子,一天天的,简直太快。感觉前一阵还在学校呢,现在已经是职工了,不得不感叹岁月的流逝实在太快。在入职的这4个月里,自己经历过欢喜,也经历过挫折。公司的试用期是6个月,我用4个......
  • 碎碎念——谈谈最近
    今日鸡汤远方降至,热血难凉。一、前言    时间飞逝,一不小心半个月又过去了,最近发生的事情特别多,在这里也记录下,也算是做个回顾。二、近期工作    熟悉我的小伙伴应该都知道上个月的时候,小编之前一直在广州出差,本来出差的时间一般是一个月的时间,但是我硬是待了40天才回来,7月1......
  • JS逆向实战13——某市公共资源交易中心Cookie混淆加密
    "本文地址:https://www.cnblogs.com/zichliang/p/17346860.html目标网站aHR0cDovL2xkZ2d6eS5obmxvdWRpLmdvdi5jbi9sZGp5engvanl4eC9saXN0LnNodG1s网站分析经过浏览器抓包,我们可知这个网站会先发出一个412请求,然后附带一个js然后返回正常的页面。经过我们代码测试可知如......
  • LCA(最近公共祖先)学习笔记
    前言没想到干完lca的时间比tarjan的还要长(我不能这么弱下去了!!)前置知识dfs序这东西有点类似于时间戳(dfn),但是它分为两部分(回溯之前和回溯之后)。并且dfs序还分为两种。这里只介绍一倍的dfs序。如上图,蓝色代表左端点,红色代表右端点,(学过Tarjan的都知道),蓝色其实就是这棵树的dfn(......
  • [Leetcode]找出链表公共结点
    力扣链接思路:先求出两个链表的长度差长链表先走差距步同时走,第一个地址相同的是交点代码:/***Definitionforsingly-linkedlist.*structListNode{*intval;*structListNode*next;*};*/structListNode*getIntersectionNode(structListNode*he......
  • 记录一次最近遇到的新网络诈骗经历,大家要提高警惕啊
    第一次接到诈骗电话,说是要求修改支付宝信息的,一开始说的确实是很迷惑人,一下子可能没法马上分辨出来,但是到后面说要加QQ操作什么什么的,那肯定就是有严重问题的,因为很多诈骗都是通过QQ来操作的,一听到这个就要警惕了。 他的诈骗流程是这样的:先是说你的支付宝花呗要调整利率,如果不......