【codevs2370】小机房的树——倍增法/链剖求LCA

题目链接

这道题就是LCA的完全模版题了,之前学倍增的时候写过一次,但是太久没复习忘了......

滚回来重新学了一波,赶紧写篇博客记录一下~

倍增法求LCA的基本思路:先dfs预处理出每个节点i的祖先2^j表示为gr[i][j]、深度deep[i]以及到祖先2^j 的距离dis[i][j]。然后对于每个询问x和y的最近公共祖先,先钦定(?)y在x的下方,然后先让y跳到x这一层,而(1<<i)&d巧妙地运用位运算让y能恰好用最少步数跳到x这一层。如果此时x==y即说明x是y的祖先,可以直接返回答案;否则就从大到小枚举倍增,这个地方也是倍增法的核心,要好好理解其中的巧妙之处。

要注意的地方:

1,根节点为0;

2.gr[i][0]即为节点i的父节点;

3.当y跳到x这一层时要注意判断x是否为y的祖先;

4.倍增结束时要记得x和y还要跳到他们的父节点上,ans不要忘记加上dis[x][0]和dis[y][0]。

其他细节详见代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
const int M=5e4+10;
using namespace std;
struct point{
    int to,next,w;
}e[M*2];
int n,m,sum=0,u,v,wi,first[M],deep[M];
bool ok[M];
int gr[M][22],dis[M][22];
int read()
{
    int ans=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){ans=ans*10+c-48;c=getchar();}
    return ans*f;
}
void add(int x,int y,int z)
{
    sum++;e[sum].to=y;e[sum].w=z;e[sum].next=first[x];first[x]=sum;
    sum++;e[sum].to=x;e[sum].w=z;e[sum].next=first[y];first[y]=sum;
}
void dfs(int x)
{
    ok[x]=1;
    for(int i=1;i<=20;i++){
        if((1<<i)>deep[x])break;
        gr[x][i]=gr[gr[x][i-1]][i-1];
        dis[x][i]=dis[x][i-1]+dis[gr[x][i-1]][i-1];
    }
    for(int i=first[x];i>0;i=e[i].next){
        int now=e[i].to;
        if(ok[now])continue;
        deep[now]=deep[x]+1;
        gr[now][0]=x;
        dis[now][0]=e[i].w;
        dfs(now);
    }
}
inline int lca(int x,int y)
{
    int ans=0;
    if(deep[x]>deep[y])swap(x,y);
    int d=deep[y]-deep[x];
    for(int i=0;i<=20;i++)
        if((1<<i)&d){
            ans+=dis[y][i];
            y=gr[y][i];
        }
    if(x==y)return ans;
    for(int i=20;i>=0;i--){
        if(gr[x][i]!=gr[y][i]){
            ans+=dis[x][i]+dis[y][i];
            x=gr[x][i];y=gr[y][i];
        }
    }
    ans+=dis[x][0]+dis[y][0];
    return ans;
}
int main()
{
    memset(first,-1,sizeof(first));
    n=read();int a,b;
    for(int i=1;i<=n-1;i++){
        u=read();v=read();wi=read();
        add(u,v,wi);
    }
    dfs(0);
    m=read();
    while(m--){
        a=read();b=read();
        printf("%d
",lca(a,b));
    }
    return 0;
}
codevs2370

 2017/9/21:

今天跟着ccz学了一波树链剖分求lca,理解完之后发现链剖无论是时间还是空间都完踩倍增诶>.<

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 const int N=5e4+10;
 5 int tot=0,first[N],n,m;
 6 int dis[N];
 7 struct node{
 8     int ne,to,w;
 9 }e[N*2];
10 struct point{
11     int son,fa,top,sz,dep;
12 }q[N];
13 void read(int &x){
14     int f=1;x=0;char c=getchar();
15     while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
16     while(c>='0'&&c<='9'){x=x*10+c-48;c=getchar();}
17     x*=f;
18 }
19 void ins(int u,int v,int w){
20     e[++tot]=(node){first[u],v,w};first[u]=tot;
21     e[++tot]=(node){first[v],u,w};first[v]=tot;
22 }
23 void dfs(int x,int fa){
24     point&w=q[x];
25     w.sz=1;
26     for(int i=first[x];i;i=e[i].ne){
27         int to=e[i].to;
28         if(to!=fa){
29             point&u=q[to];dis[to]=dis[x]+e[i].w;
30             u.dep=w.dep+1;u.fa=x;
31             dfs(to,x);w.sz+=u.sz;if(q[w.son].sz<u.sz)w.son=to;
32         }
33     }
34 }
35 void dfs1(int x,int tp){
36     point&w=q[x];w.top=tp;
37     for(int i=first[x];i;i=e[i].ne){
38         int to=e[i].to;
39         if(to!=w.fa){
40             if(to==w.son)dfs1(to,tp);
41             else dfs1(to,to);
42         }
43     }
44 }
45 int lca(int x,int y){
46     int a=q[x].top,b=q[y].top;
47     while(a!=b){
48         if(q[a].dep>q[b].dep)x=q[a].fa,a=q[x].top;
49         else y=q[b].fa,b=q[y].top;
50     }
51     if(q[x].dep<q[y].dep)return x;
52     return y;
53 }
54 int main(){
55     read(n);
56     for(int i=1,a,b,c;i<n;i++){
57         read(a);read(b);read(c);a++;b++;ins(a,b,c);
58     }
59     dis[1]=0;dfs(1,0);dfs1(1,1);
60     read(m);
61     while(m--){
62         int u,v;read(u);read(v);u++;v++;
63         printf("%d
",dis[u]+dis[v]-2*dis[lca(u,v)]);
64     }
65     return 0;
66 }
链剖求lca
原文地址:https://www.cnblogs.com/JKAI/p/7301523.html