加载中...

LCA 返回最近公共祖先:预处理

三种祖先关系 a是b祖先 b是a祖先 a和b不是祖先关系

树上多个点的LCA,就是DFS序最小的和DFS序最大的这两个点的LCA

必备:知道根节点 必须存下来

有可能跳过根节点
int depth[N],f[][N];//N为(log节点数)+1
int q[N];

从根节点开始预处理;
需要设置0号点为哨兵
询问 p=lca(a,b)

向上标记法on:从一个点向根节点遍历标记公共祖先 ,然后另一个点也向上走 走到第一个标记过的位置 就是祖先

倍增法ologn 预处理nlogn 查询logn:f[i][j]从i开始 向上走2^j次方 走到的节点

f[i][0]i点向上走一步走到的节点 f[i][1]向上走两步走到的节点 f[i][2]向上走四步发现不存在值为空集
当j>0 f[i][j]=f[i][j-1]基础上2^j-1步
depth表示深度从上往下看的 根节点深度是1 i表示i节点的深度
于是lca
哨兵:如果f[i][j]跳过根节点那么设为0 0节点的深度为0
第一步让两个点跳到同一层: t=相差depth(x)-depth(y)层 根据t的二进制跳 如果跳了不在y的上面那就跳:depth(f(x,k))>depth(y) 所以挑了之后就还能跳
第二步让两个点同时往上跳,直到跳到最近公共祖先的下一层:此时最近公共祖先为f[i][0]

祖孙查询https://www.acwing.com/activity/content/problem/content/1537/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 40010,M=2*N;

int n,m;
int h[N], e[M], ne[M], idx;
int depth[N],fa[N][16];
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;//0号哨兵点深度为0,根节点深度为1
    int hh=0,tt=0;//bfs队列
    q[0]=root;
    while(hh<=tt){
        int t=q[hh++];
        for (int i = h[t]; ~i ; i =ne[i] ){
            int j=e[i];//对于当前点的子节点
            if(depth[j]>depth[t]+1){//既然连了边,又比+1大说明是前面设置0正无穷的缘故
                depth[j]=depth[t]+1;
                q[++tt]=j;
                fa[j][0]=t;
                for (int k = 1; k <= 15; k ++ ){//因为fa初始化是0所以只要走不上求自动为0
                    fa[j][k]=fa[fa[j][k-1]][k-1];//j点在上面第k-1的祖先往上走k-1步
                }
            }
        }
    }
}
int lca(int a,int b){//两个fork从大到小实现 
    if(depth[a]<depth[b]) swap(a,b);//要求a在b的下面 不满足就换
    for (int k = 15; k >=0; k -- ){//从大到小开始走
        if(depth[fa[a][k]]>=depth[b])
            a=fa[a][k];
    }
    if(a==b) return a;
    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];//向上走一步
}
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);//预处理
    cin >> 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;
    }
    return 0;
}

tarjan 离线求LCA

一递归搜索分成三类+并查集查询
1.已经遍历到的点且回溯的点 2.正在搜素的分支 3.还未搜索到的点
求公共祖先就是看下在1的a点的祖宗节点是谁

距离https://www.acwing.com/problem/content/1173/

xy的距离是 d[x]+[y]-2*d[p];

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define pii pair<int,int>
const int N = 20010,M=2*N;

int n,m;
int h[N], e[M], ne[M], idx,w[M];
int p[N];
int dist[N];
vector<pii>query[N];
// query[i][first][second] first存查询距离i的另外一个点j,second存查询编号idx

int st[N];
int res[N];
void add(int a, int b, int c)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
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);
    }
}
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void tarjan(int u){
    st[u]=1;//表示正在搜
    for (int i = h[u]; ~i ; i =ne[i] ){
        int j=e[i];// u这条路上的根节点的左下的点用并查集合并到根节点
        if(!st[j]){
            tarjan(j);//往左边搜 搜完在合并
            p[j]=u;//从左下会输后把左边的点合并到根节点
        }
    }
    //对于当前点u 搜索所有和u相关的查询 这步能及时处理查询 不然合并了就查不出来了
    for ( auto item:query[u] ){//遍历和u相关的查询
        int y=item.first,id=item.second;
        if(st[y]==2){//如果这个点已经是之前搜索过的 那么就可以找了 
            int anc=find(y);//直接找到两者的公共祖先
            res[id]=dist[u]+dist[y]-2*dist[anc];
            
        }
    }
    st[u]=2;//表示已经搜过
}
int main()
{
    // int n,m;
    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});//数组存入关于a节点的询问,另一个节点和第几个查询
            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]<<endl;
    }
    return 0;
}

求对AB树上集合的某一个点删除后求lca a的值比b的值大的方案

因为集合的点确定而且排列组合麻烦所以枚举点
树上多个点的LCA,就是DFS序最小的和DFS序最大的这两个点的LCA

//deep数组记录的是树上每个节点的深度,fa数组记录的是每个点的父节点,这两个是倍增lca需要的
int deepA[N], faA[N][31], deepB[N], faB[N][31];

//st数组记录每个点的dfs序,w数组记录每个点的权值
int stA[N], stB[N], wA[N], wB[N];

//树数组
vector<int>treeA[N], treeB[N];

bool cmpA(int a, int b)
{
    return stA[a] < stA[b];
}

bool cmpB(int a, int b)
{
    return stB[a] < stB[b];
}

// dfs,用来为 lca 算法做准备。接受两个参数:当前结点,它的父亲节点,cnt的数注意传入指针修改
void dfsA(int root, int fno, int& cnt) {
    // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
    faA[root][0] = fno;

    stA[root] = cnt++;//记录dfs序!!
    deepA[root] = deepA[faA[root][0]] + 1;
    // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
    // 2^(i-1) 的祖先节点。
    for (int i = 1; i < 31; ++i) {
        faA[root][i] = faA[faA[root][i - 1]][i - 1];
    }
    // 遍历子节点来进行 dfs。
    int sz = treeA[root].size();
    for (int i = 0; i < sz; ++i) {
        if (treeA[root][i] == fno) continue;
        dfsA(treeA[root][i], root, cnt);
    }
}

void dfsB(int root, int fno, int& cnt) {
    faB[root][0] = fno;
    stB[root] = cnt++;
    deepB[root] = deepB[faB[root][0]] + 1;
    for (int i = 1; i < 31; ++i) {
        faB[root][i] = faB[faB[root][i - 1]][i - 1];
    }
    int sz = treeB[root].size();
    for (int i = 0; i < sz; ++i) {
        if (treeB[root][i] == fno) continue;
        dfsB(treeB[root][i], root, cnt);
    }
}

// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lcaA(int x, int y) {
    // 令 y 比 x 深。
    if (deepA[x] > deepA[y]) swap(x, y);
    // 令 y 和 x 在一个深度。
    int tmp = deepA[y] - deepA[x], ans = 0;
    for (int j = 0; tmp; ++j, tmp >>= 1)
        if (tmp & 1) y = faA[y][j];
    // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
    if (y == x) return x;
    // 不然的话,找到第一个不是它们祖先的两个点。
    for (int j = 30; j >= 0 && y != x; --j) {
        if (faA[x][j] != faA[y][j]) {
            x = faA[x][j];
            y = faA[y][j];
        }
    }
    // 返回结果。
    return faA[x][0];
}

int lcaB(int x, int y) {
    if (deepB[x] > deepB[y]) swap(x, y);
    int tmp = deepB[y] - deepB[x], ans = 0;
    for (int j = 0; tmp; ++j, tmp >>= 1)
        if (tmp & 1) y = faB[y][j];
    if (y == x) return x;
    for (int j = 30; j >= 0 && y != x; --j) {
        if (faB[x][j] != faB[y][j]) {
            x = faB[x][j];
            y = faB[y][j];
        }
    }
    return faB[x][0];
}

void solve()
{
    int n, m, x;
    cin >> n >> m;
    vector<int>ansA(m), ansB(m);
    for (int i = 0; i < m; i++)cin >> ansA[i], ansB[i] = ansA[i];
    for (int i = 1; i <= n; i++)cin >> wA[i];
    for (int i = 2; i <= n; i++)
    {
        cin >> x;
        treeA[x].push_back(i);
    }
    int cnt = 1;
    //预处理lca,并且求得每个点的dfs序
    dfsA(1, 0, cnt);
    for (int i = 1; i <= n; i++)cin >> wB[i];
    for (int i = 2; i <= n; i++)
    {
        cin >> x;
        treeB[x].push_back(i);
    }
    cnt = 1;
    dfsB(1, 0, cnt);
    //按照dfs序对k集合的点进行升序排序
    sort(ansA.begin(), ansA.end(), cmpA);
    sort(ansB.begin(), ansB.end(), cmpB);
    cnt = 0;
    
    //我们以A树的k集合为标准枚举所有的点
    for (int i = 0; i < m; i++)
    {
        //x记录k集合中dfs序最小的点,y记录最大的点
        int xA = ansA[0], yA = ansA[m - 1], xB = ansB[0], yB = ansB[m - 1];
        //如果最大的点被删除,我们取第二大的
        if (i == 0)xA = ansA[1];
        //如果最小的点被删除,我们取第二小的
        else if (i == m - 1)yA = ansA[m - 2];

        if (xB == ansA[i])xB = ansB[1];
        else if (yB == ansA[i])yB = ansB[m - 2];

        //求的两个树的最近公共祖先
        int xlca = lcaA(xA, yA), ylca = lcaB(xB, yB);
        if (wA[xlca] > wB[ylca])cnt++;
    }
    cout << cnt << endl;
}

signed main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t = 1;
    //cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

posted @ 2022-07-25 14:49  liang302  阅读(57)  评论(0编辑  收藏  举报