【lca】lca的tarjan写法 poj1330

今天看了一下午的lca的tarjan写法,发现果真还是不能全部理解,不过还是有大部分能够理解,就将自己的思路写在这里备忘吧。。

tarjan算法是基于并查集与dfs的一种离线算法

tarjan算法的步骤是(当dfs到节点u时):
1 在并查集中建立仅有u的集合,设置该集合的祖先为u,就是普通的并查集,fa[i] = i;
1 对u的每个孩子v:
   1.1 tarjan之
   1.2 合并v到父节点u的集合,确保集合的祖先是u
2 设置u为已遍历
3 处理关于u的查询,若查询(u,v)中的v已遍历过,则LCA(u,v)=v所在的集合的祖先







图上已经讲解的很详细了,这里是并查集的一个应用。。

不过光看思想并不能解决问题,于是我找了一道lca的果题,poj1330

题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=11136

Time Limit: 1000MS Memory Limit: 10000KB 64bit IO Format: %I64d & %I64u

 Status

Description

A rooted tree is a well-known data structure in computer science and engineering. An example is shown below: 


In the figure, each node is labeled with an integer from {1, 2,...,16}. Node 8 is the root of the tree. Node x is an ancestor of node y if node x is in the path between the root and node y. For example, node 4 is an ancestor of node 16. Node 10 is also an ancestor of node 16. As a matter of fact, nodes 8, 4, 10, and 16 are the ancestors of node 16. Remember that a node is an ancestor of itself. Nodes 8, 4, 6, and 7 are the ancestors of node 7. A node x is called a common ancestor of two different nodes y and z if node x is an ancestor of node y and an ancestor of node z. Thus, nodes 8 and 4 are the common ancestors of nodes 16 and 7. A node x is called the nearest common ancestor of nodes y and z if x is a common ancestor of y and z and nearest to y and z among their common ancestors. Hence, the nearest common ancestor of nodes 16 and 7 is node 4. Node 4 is nearer to nodes 16 and 7 than node 8 is. 

For other examples, the nearest common ancestor of nodes 2 and 3 is node 10, the nearest common ancestor of nodes 6 and 13 is node 8, and the nearest common ancestor of nodes 4 and 12 is node 4. In the last example, if y is an ancestor of z, then the nearest common ancestor of y and z is y. 

Write a program that finds the nearest common ancestor of two distinct nodes in a tree. 

Input

The input consists of T test cases. The number of test cases (T) is given in the first line of the input file. Each test case starts with a line containing an integer N , the number of nodes in a tree, 2<=N<=10,000. The nodes are labeled with integers 1, 2,..., N. Each of the next N -1 lines contains a pair of integers that represent an edge --the first integer is the parent node of the second integer. Note that a tree with N nodes has exactly N - 1 edges. The last line of each test case contains two distinct integers whose nearest common ancestor is to be computed.

Output

Print exactly one line for each test case. The line should contain the integer that is the nearest common ancestor.

Sample Input

2
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7
5
2 3
3 4
3 1
1 5
3 5

Sample Output

4
3


其实这道题用RMQ,倍增也可以做,不过今天看的tarjan,就用tarjan做吧,具体看代码注释

【代码】

先贴一个标程

//O(n+Q)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;

#define MAXN 10001

int n,fa[MAXN];
int rank[MAXN];
int indegree[MAXN];
int vis[MAXN];
vector<int> hash[MAXN],Qes[MAXN];
int ances[MAXN];//祖先


void init(int n)
{
    for(int i=0;i<=n;i++)
    {
        fa[i]=i;
        rank[i]=0;
        indegree[i]=0;
        vis[i]=0;
        ances[i]=0;
        hash[i].clear();
        Qes[i].clear();
    }
}

int find(int x)
{
    if(x != fa[x])
        fa[x]=find(fa[x]);
    return fa[x];
}

void unio(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx==fy) return ;
    if(rank[fy]<rank[fx])
        fa[fy]=fx;
    else
    {
        fa[fx]=fy;
        if(rank[fx]==rank[fy])
            rank[fy]++;
    }
}

void Tarjan(int u)
{
    ances[u]=u;
    int i,size = hash[u].size();
    for(i=0;i<size;i++)
    {
        Tarjan(hash[u][i]);//递归处理儿子
        unio(u,hash[u][i]);//将儿子父亲合并,合并时会将儿子的父亲改为u
        ances[find(u)]=u;//此时find(u)仍为u,即
    }
    vis[u]=1;
    
    //查询
    size = Qes[u].size();
    for(i=0;i<size;i++)
    {
        if(vis[Qes[u][i]]==1)//即查询的另一个结点开始已经访问过,当前的u在此回合访问。
        {
            printf("%d\n",ances[find(Qes[u][i])]);//由于递归,此时还是在u
            return;
        }
    }
}

int main()
{
    int t;
    int i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        init(n);
        int s,d;
        for(i=1;i<=n-1;i++)
        {
            scanf("%d%d",&s,&d);
            hash[s].push_back(d);
            indegree[d]++;
        }
        scanf("%d%d",&s,&d);
        Qes[s].push_back(d);
        Qes[d].push_back(s);
        for(j=1;j<=n;j++)
        {
            if(indegree[j]==0)
            {
                Tarjan(j);
                break;
            }
        }
    }
    return 0;
}

再贴一个自己写的,但不知道哪错了,再找找错误吧

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define mem(a,x) memset(a, x , sizeof(a))
using namespace std;

struct edge{
	int v,next;
}e[10000 + 5];
int ind[10000 + 5];//记录每个点的入度
int fa[10000 + 5];//并查集用
int head[10000 + 5], k = 1;
bool vis[10000 + 5];//判断是否遍历过
int rnk[10000 + 5];//就是这个不知道什么用,觉得像是并查集压缩路径的优化
int anc[10000 + 5];//每个点的祖先
vector<int >que[10000 + 5];//与x有关的询问放在q[x]中
int T;
int root;
int n;
 
void init()//初始化
{
	mem(vis,0);
	mem(head,0);
	mem(fa,0);
	mem(ind,0);
	mem(rnk,0);
	mem(e,0);
	k = 1;
	for(int i = 1; i <= n; i++)
	fa[i] = i,anc[i] = 0,que[i].clear();
} 
void adde(int u, int v)//加边,其实觉得用vector数组更好用
{
	e[k].v = v;
	e[k].next = head[u];
	head[u] = k++;
}
int find(int x)//并查集,不解释
{
	return fa[x] == x ? x : fa[x] = find(fa[x]);
} 
void Union(int x, int y)//合并
{
	int fx = find(x),fy = find(y);
	if(fx == fy)return ;
	if(rnk[fx] > rnk[fy]) fa[fy] = fx;
	else fa[fx] = fy, rnk[fy] += rnk[fx] == rnk[fy];
}

void tarjan(int u)
{
	anc[u] = u;//u是u集合的祖先
	for(int i = head[u]; i ; i = e[i].next)//遍历边
	{
		int v = e[i].v;
		if(!vis[v])
		{
			tarjan(v);
			Union(u,v);
			anc[find(u)] = u;//保证u的子树的祖先是u
		}
	}
	vis[u] = 1;
	for(int i = 0; i < que[u].size(); i++)//处理查询
	if(vis[que[u][i]]){
	printf("%d\n",anc[find(que[u][i])]);
	return;
	}
}
int main()
{
	scanf("%d", &T);
	while(T--)
	{
		init();
		scanf("%d", &n);
		for(int i = 1; i < n; i++)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			adde(u,v);
			ind[v]++;
		}
		for(int i = 1; i <= n; i++)
		if(!ind[i]){
			root = i;
			break;
		}
		int a,b;
		scanf("%d%d", &a, &b);
		que[a].push_back(b);que[b].push_back(a);
		tarjan(root);
	}
	return 0;
}

暂时就这些了,到时再来慢慢完善吧。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目:使用 JavaScript 编写的杀死幽灵游戏(附源代码) 杀死鬼魂游戏是使用 Vanilla JavaScript、CSS 和 HTML 画布开发的简单项目。这款游戏很有趣。玩家必须触摸/杀死游荡的鬼魂才能得分。您必须将鼠标悬停在鬼魂上 - 尽量得分。鬼魂在眨眼间不断从一个地方移动到另一个地方。您必须在 1 分钟内尽可能多地杀死鬼魂。 游戏制作 这个游戏项目只是用 HTML 画布、CSS 和 JavaScript 编写的。说到这个游戏的特点,用户必须触摸/杀死游荡的幽灵才能得分。游戏会根据你杀死的幽灵数量来记录你的总分。你必须将鼠标悬停在幽灵上——尽量得分。你必须在 1 分钟内尽可能多地杀死幽灵。游戏还会显示最高排名分数,如果你成功击败它,该分数会在游戏结束屏幕上更新。 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox。要玩游戏,首先,单击 index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值