您的位置:首页 > 其它

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

2015-09-19 17:47 393 查看
题目描述 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了三遍!!!

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

裸的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
*对每个数据进行计算 
*/


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

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

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

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

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

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

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

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

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

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

——我欲穿花寻路,直入白云深处,浩气展虹霓。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: