【日常学习】【倍增LCA】codevs2370 小机房的树题解

本文详细介绍了解决树上最近公共祖先(LCA)问题的一种有效方法——倍增法,并通过具体实例展示了算法的应用过程。

题目描述 Description

小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力

输入描述 Input Description
第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。
第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点
输出描述 Output Description

一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。

样例输入 Sample Input

3

1 0 1

2 0 1

3

1 0

2 0

1 2

样例输出 Sample Output

1

1

2

 

数据范围及提示 Data Size & Hint

1<=n<=50000, 1<=m<=75000, 0<=c<=1000

显然这是裸的LCA 我们用倍增来写 因为tanjan不会

但我们一定要注意:邻接表要开两倍!!!邻接表要开两倍!!!邻接表要开两倍!!!重要的事情说三遍!因为你WA了三遍!!!


////codevs2370 小机房的树 LCA最近公共祖先 目前现状:A一半W一半 大数据W 静态查代码 
//copyright by ametake
#include
#include
#include
using namespace std;

const int maxn=50000+10;
int p[maxn][20],cost[maxn][20];//cost表示从节点i向上蹦2^j至某一节点t的路程中花费的精力和
int father[maxn];
int depth[maxn];
int power[20];
int et=0,t[maxn],val[maxn],head[maxn],next[maxn];//边的编号,去路,权值,以同一节点开始的第一条边的编号,这条边连接的下一条编的编号 
int n;

void add(int from,int to,int v)
{
    et++;
    t[et]=to;
    val[et]=v;
    next[et]=head[from];
    head[from]=et;
}

void makepower()
{
    power[0]=1;
    power[1]=2;
    int cnt=1;
    for (int i=2;i<20;i++)
    {
        power[cnt+1]=power[cnt]*2;
        cnt++;
    }
}

void dfs(int now)
{
    int i=head[now];
    while (i!=0)
    {
        if (depth[t[i]])
        {
        	i=next[i];
        	continue;
		}
        depth[t[i]]=depth[now]+1;
        father[t[i]]=now;//notice
        cost[t[i]][0]=val[i];
        dfs(t[i]);
    }
}

void doit()
{
    for (int i=1;i<=n;i++)
    {
        p[i][0]=father[i];
    } 
    for (int j=1;j<20;j++)
    {
        for (int i=1;i<=n;i++)
        {
            p[i][j]=p[p[i][j-1]][j-1];
            cost[i][j]=cost[p[i][j-1]][j-1]+cost[i][j-1];
        }
    }
}

int lca(int u,int v)
{
    int ans=0;
    if (depth[u]=0;j--)
    {
        if (dt==0) break;
        if (power[j]<=dt)//<= not <
        {
            dt-=power[j];
            ans+=cost[u][j];
            u=p[u][j];
        }
    }
    for (int j=19;j>=0;j--)
    {
        if (p[u][j]!=p[v][j])
        {
            ans+=cost[u][j];
            ans+=cost[v][j];
            v=p[v][j];
            u=p[u][j];
        }
    }
    if (u!=v)
    {
    	ans=ans+cost[u][0];
        ans=ans+cost[v][0];
    }
    return ans;
}


int main()
{
    freopen("1.txt","r",stdin);
    scanf("%d",&n);
    int u,v,c;
    memset(head,0,sizeof(head));
    for (int i=1;i

详细的注释,可以看洛谷某道题目的代码 比赛还没有结束 但我估计这里的代码不会被搜到吧?

裸的LCA 只不过要把加改为异或而已

//B
//所谓树上倍增= =LCA没过 这个要能过就是奇迹



//codevs2370 小机房的树 LCA最近公共祖先 目前现状:A一半W一半 大数据W 静态查代码 
//copyright by ametake
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn=200000+20;
int p[maxn][22],cost[maxn][22];//cost表示从节点i向上蹦2^j至某一节点t的路程中花费的精力和
int father[maxn];//某个点的父亲 
int depth[maxn];//某个点的层次 
int power[22];
int et=0,t[maxn],val[maxn],head[maxn],next[maxn];//边的编号,去路,权值,以同一节点开始的第一条边的编号,这条边连接的下一条编的编号 
int n;

void add(int from,int to,int v)
{
    et++;
    t[et]=to;
    val[et]=v;
    next[et]=head[from];
    head[from]=et;
}
/*
void makepower()
{
    power[0]=1;
    power[1]=2;
    int cnt=1;
    for (int i=2;i<20;i++)
    {
        power[cnt+1]=power[cnt]*2;
        cnt++;
    }
}
*/
void dfs(int now)
{
    int i=head[now];//开始时,i是起点为1的第一条边 now==1 
    while (i!=0)//
    {
        if (depth[t[i]])//如果这条边的终点有层次,也就是访问过,是原节点的父亲 
        {
        	i=next[i];//更新i 跳到1为起点的下一条边 
        	continue;//不再进行后面的 直接进入下一次循环 
		}
        depth[t[i]]=depth[now]+1;//i这条边的终点的层次是now(起点)层次+1 
        father[t[i]]=now;//notice 终点的父亲是起点 
        cost[t[i]][0]=val[i];//终点向上一位的总异或值是这条边的边权   !!!务必注意输入两个相同的点输出0 
        dfs(t[i]);//搜索以终点为起点的所有边 即终点的儿子们 
    }
}

void doit()
{
    for (int i=1;i<=n;i++)
    {
        p[i][0]=father[i];//每个点上面一个点是他的父亲 
    } 
    for (int j=1;j<20;j++)
    {
        for (int i=1;i<=n;i++)
        {
            p[i][j]=p[p[i][j-1]][j-1];//求祖先 
            cost[i][j]=((cost[p[i][j-1]][j-1])^(cost[i][j-1]));//从i向上数2^j个点 如果j是1 i=1,ij=3 这条路上的异或值  
        }
    }
}

int lca(int u,int v)
{
    int ans=0;
    if (u==v) return 0; 
    if (depth[u]<depth[v]) swap(u,v);//u是深度深的那个 u是儿子 先蹦 
    int dt=depth[u]-depth[v];
    /*for (int j=19;j>=0;j--)//可以尝试每次更新dt 做完后跳到同一层 
    {
        if (dt==0) break;
        if (power[j]<=dt)//<= not <
        {
            dt-=power[j];
            ans=(cost[u][j]^ans);
            u=p[u][j];
        }
    }*/
    for (int j=19;j>=0;j--)//可以尝试每次更新dt 做完后跳到同一层 
    {
        if (dt==0) break;
        if ((1<<j)<=dt)//<= not <
        {
            dt-=(1<<j);
            ans=(cost[u][j]^ans);
            u=p[u][j];
        }
    }
    if (u==v) return ans;
    for (int j=19;j>=0;j--)//做完这一步 如果不是父子关系两个有共同的父亲 
    {
        if (p[u][j]!=p[v][j])
        {
            ans=(cost[u][j]^ans);
            ans=(cost[v][j]^ans);
            v=p[v][j];
            u=p[u][j];
        }
    }
    if (u!=v)
    {
    	ans=(ans^cost[u][0]);
        ans=(ans^cost[v][0]);
    }
    return ans;
}


int main()
{
    freopen("1.txt","r",stdin);
    scanf("%d",&n);
    int u,v,c;
    memset(head,0,sizeof(head));
    for (int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&c);
        add(u,v,c);
        add(v,u,c);
    }
    memset(father,0,sizeof(father));
    memset(depth,0,sizeof(depth)); 
    //makepower();//作用:求出2的幂次方 
    father[1]=1;//点1的父亲和层次都是1 也就是说1是根 
    depth[1]=1;
    dfs(1);//从1为起点的边开始搜索 经检验无误XD 
    doit();//求值 
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&u,&v);
        printf("%d\n",lca(u,v));
    }
    while(1);
    return 0;
}

/*
*B的处理思路
*首先读入边 无向图邻接表存
*由于要求的是路径上边权的异或,还是和机房一样,还是先跑一边LCA然后求cost 区别是不是求和是异或 
*思路参考下面 
*/

/*
*首先读入边 邻接表存储
*然后DFS一次,求层次和father
*然后预处理出来p和cost
*对每个数据进行计算 
*/ 

需要注意的是 这个程序原本计算二的幂次方存储 后来发现位运算是个好东西 

今天忙了一天比赛以及未来规划 也够累了 想去刷几个题目 还要刷作业 所以要加油了

安排了下明年暑假 这么早安排没啥实用性 但也算吃了颗定心丸 然后发现暑假时间好紧挤不出来 所以现在要更加努力努力好好学代码好好学文化课打好基础 这样暑假学软件硬件和预科就要轻松些了 

今天我有三个梦想:我要环游世界,我要做一流的极客和技术宅,我要做一个有自己的思想、有文化、有修养的人。

为了实现我的梦想,我要在高三一年努力打好基础 创造必要条件 为自己的梦想提供基础保障

为了实现我的梦想,我要在高三专注拼搏,专注只做这一件事,为自己谋求最优质的资源和环境

为了实现我的梦想,高三一结束,我即踏上征程,一天也等不及

希望一年后的今天 当我再看到这篇文章 梦想已经在路上

希望十年后的今天 当我再看到这篇文章 我能露出欣慰的微笑而不是无奈或悔恨的眼泪 因为我有勇气 我能做到 我会努力 我知道我年轻 我知道我的人生只有这一次 精神快乐比物质富足更重要 我知道我现在追求的是什么 我也会一直学习 不断充实和修正我的追求


写这些斗志昂扬的话,那么就努力去做吧。


——我欲穿花寻路,直入白云深处,浩气展虹霓。



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值