zoukankan      html  css  js  c++  java
  • LCA__st算法&&树上倍增

    st表

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    int go[2000005];
    int nxt[2000005];
    int adj[2000005];
    int pos[2000005];
    int st[2000005][22];
    int dep[2000005];
    int aa[2000005];
    int lg[2000005];
    int ecnt,cnt,m,n,s,x,y,a,b;
    void add(int u,int v) {   //建链前
        go[++ecnt]=v;
        nxt[ecnt]=adj[u];
        adj[u]=ecnt;
    }
    void dfs(int u,int pre) {
        dep[u]=dep[pre]+1;  //首先预处理出深度
        aa[++cnt]=u;  //DFS序
        pos[u]=cnt;   //第几个出现的
        for(int e=adj[u]; e; e=nxt[e]) {
            if(go[e]!=pre) {
                dfs(go[e],u);
                aa[++cnt]=u;
            }
        }
    }
    int MIN(int p,int q) {
        if(dep[p]<dep[q]) return p;
        else return q;
    }
    void build() {
        for(int i=1; i<=cnt; i++) {
            st[i][0]=aa[i];
        }
        for(int i = 1, j = 0; i <= cnt; i++){
            if(1 << (j + 1) == i) j++;
            lg[i] = j;    
        }
        for(int j=1; j<=lg[cnt]; j++) {
            for(int i=1; i + (1 << j) - 1 <=cnt; i++) {
                st[i][j]=MIN(st[i][j-1],st[i+(1 << (j - 1))][j-1]);
            }
        }
    }
    /*
    读入两个节点,查询它们第一次出现的位置
    在这两个位置之间的区间查询最小深度的节点,该节点即为最近公共祖先
    */
    int lca(int l,int r) {
        int xx=pos[l];
        int yy=pos[r];
        if(xx>yy) swap(xx,yy);
        int k=lg[yy-xx+1];
        return MIN(st[xx][k],st[yy-(1 << k)+1][k]);
    }
    template <class T>  //快速读入
    void read(T &x){
        char c;
        bool op = 0;
        while(c = getchar(), c < '0' || c > '9')
            if(c == '-') op = 1;
        x = c - '0';
        while(c = getchar(), c >= '0' && c <= '9')
            x = x * 10 + c - '0';
        if(op) x = -x;
    }
    template <class T>  //快速输出
    void write(T x){
        if(x < 0) putchar('-'), x = -x;
        if(x >= 10) write(x / 10);
        putchar('0' + x % 10);    
    }
    int main() {
        //scanf("%d%d%d",&n,&m,&s);
        read(n), read(m), read(s);
        for(int i=1; i<=n-1; i++) {
            //scanf("%d%d",&x,&y);
            read(x), read(y);
            add(x,y);
            add(y,x);
        }
        dfs(s,0);
        build();
        for(int i=1; i<=m; i++) {
            //scanf("%d%d",&a,&b);
            //printf("%d
    ",lca(a,b));
            read(a), read(b);
            write(lca(a, b)), putchar('
    ');
        }
        return 0;
    }

    树上倍增和st表的思路一样,只是实现方法不同

    /*
    倍增求LCA:
    father【i】【j】表示节点i往上跳2^j次后的节点
    可以转移为
    father【i】【j】=father【father【i】【j-1】】【j-1】
    (此处注意循环时先循环j,再循环i)
    然后dfs求出各个点的深度depth
    整体思路:
    先比较两个点的深度,如果深度不同,先让深的点往上跳,浅的先不动,
    等两个点深度一样时,if 相同 直接返回,if 不同 进行下一步;如果不同
    ,两个点一起跳,j从大到小枚举(其实并不大),如果两个点都跳这么多后
    ,得到的点相等,两个点都不动(因为有可能正好是LCA也有可能在LCA上方)
    ,知道得到的点不同,就可以跳上来,然后不断跳,两个点都在LCA下面那层,
    所以再跳1步即可,当father【i】【j】中j=0时即可,就是LCA,返回值结束
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<queue>
    using namespace std;
    vector <int> g[100010];
    int father[100010][40]={0};
    int depth[100010]={0};
    int n,m;
    bool visit[10010]={false};
    int root;
    void dfs(int u)//深搜出各点的深度,存在depth中
    {
        int i;
        visit[u]=true;
        for (i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if ( !visit[v] )
            {
                depth[v]=depth[u]+1;
                dfs(v);
            }
        }
    } 
    void bz()//fa数组的预处理 
    {
        int i,j;
        for (j=1;j<=30;j++)
            for (i=1;i<=n;i++)
                father[i][j]=father[father[i][j-1]][j-1];
    }//倍增,处理father数组,详情参照上述讲解 
    int LCA(int u,int v)
    {
        if ( depth[u]<depth[v] ) 
        {
            int temp=u;
            u=v;
            v=temp;
        }//保证深度大的点为u,方便操作 
        int dc=depth[u]-depth[v];
        int i;
        for (i=0;i<30;i++)//值得注意的是,这里需要从零枚举 
        {
            if ( (1<<i) & dc)//从大到小二分
            u=father[u][i];//意思是跳2^j步不一样,就跳,否则不跳
        }
        //上述操作先处理较深的结点,使两点深度一致 
        if (u==v) return u;//如果深度一样时,两个点相同,直接返回 
        for (i=29;i>=0;i--)//从大到小二分。 
        {
            if (father[u][i]!=father[v][i])//跳2^j步不一样,就跳,否则不跳 
            {
                u=father[u][i];
                v=father[v][i];
            }
        }
        u=father[u][0];//上述过程做完,两点都在LCA下一层,所以走一步即可 
        return u;
    }
    int main()
    {
        int i,j;
        scanf("%d",&n);
        for (i=0;i<=n;i++)
        g[i].clear();
        for (i=1;i<n;i++)
        {
            int a,b;
            int root;
            scanf("%d%d",&a,&b);
            g[a].push_back(b);
            father[b][0]=a;//a^0为1,所以fa[a][0]代表a向上走一格 
            if (father[a][0]==0)//如果节点的根为0,证明该节点为根节点 
            root = a;
        }
        depth[root]=1;
        dfs(root);
        bz();
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d",LCA(x,y));
        return 0;
    }
    只想找一个不会伤害我的人
  • 相关阅读:
    Triangle
    Pascal's Triangle II
    Pascal's Triangle
    Populating Next Right Pointers in Each Node II
    Populating Next Right Pointers in Each Node
    [c++]this指针理解
    [oracle]一个最简单的oracle存储过程"proc_helloworld"
    Oracle 的 INSERT ALL和INSERT FIRST
    Linux2.6 内核的 Initrd 机制解析
    /boot/grub/menu.lst详解
  • 原文地址:https://www.cnblogs.com/DukeLv/p/7874462.html
Copyright © 2011-2022 走看看