HDU 5266 bc# 43 LCA+跳表

学了一发LCA的倍增算法+跳表维护。

先说说LCA倍增算法,思路是fa[i][j]求的是i结点的2^j倍的祖先,其中2^0就是父结点了。所以可以递推fa[i][j]=fa[fa[i][j-1]][j-1]。

当求LCA时,设深度u>v,则先倍增把u提到v的同等深度,若u==v,lca就是u,否则,两点同时倍增,直到最小深度的p[u][j]!=p[v][j],此时他们的父亲p[u][0]即lca。

可以看大牛http://www.cnblogs.com/OUSUO/p/3805715.html?utm_source=tuicool,先转一发。

1. DFS预处理出所有节点的深度和父节点

inline void dfs(int u)
{
    int i;
    for(i=head[u];i!=-1;i=next[i])
    {
        if (!deep[to[i]])
        {
            deep[to[i]] = deep[u]+1;
            p[to[i]][0] = u; //p[x][0]保存x的父节点为u;
            dfs(to[i]);
        }
    }
}

2. 初始各个点的2^j祖先是谁 ,其中2^j(j=0...log(该点深度))倍祖先,1倍祖先就是父亲,2倍祖先是父亲的父亲......。

void init()
{
    int i,j;
    //p[i][j]表示i结点的第2^j祖先
    for(j=1;(1<<j)<=n;j++)
        for(i=1;i<=n;i++)
            if(p[i][j-1]!=-1)
                p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}

3.从深度大的节点上升至深度小的节点同层,如果此时两节点相同直接返回此节点,即lca。

否则,利用倍增法找到最小深度的p[a][j]!=p[b][j],此时他们的父亲p[a][0]即lca。

int lca(int a,int b)//最近公共祖先
{
    int i,j;
    if(deep[a]<deep[b])swap(a,b);
    for(i=0;(1<<i)<=deep[a];i++);
    i--;
    //使a,b两点的深度相同
    for(j=i;j>=0;j--)
        if(deep[a]-(1<<j)>=deep[b])
            a=p[a][j];
    if(a==b)return a;
    //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
    for(j=i;j>=0;j--)
    {
        if(p[a][j]!=-1&&p[a][j]!=p[b][j])
        {
            a=p[a][j];
            b=p[b][j];
        }
    }
    return p[a][0];
}

维护跳表的思想其实和ST算法是一样的,dp[i][j]表示区间i到i+(2^j)-1的LCA,由底往上递推就是dp[i][j]=LCA(dp[i][j-1],dp[i+(1<<j)][j-1])。即可。查询时,也按照跳表查询就可以了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int N=300010;

struct Edge{
	int v,next;
}edge[N*2];
int head[N],tot;
int fa[N][22],dp[N][22];
int dep[N];

void addedge(int u,int v){
	edge[tot].v=v;
	edge[tot].next=head[u];
	head[u]=tot++;
}

void BFS(int rt){
	queue<int>que;
	que.push(rt);
	fa[rt][0]=-1; dep[rt]=1;
	while(!que.empty()){
		int u=que.front();
		que.pop();
		for(int i=1;i<=20;i++){
			if(fa[u][i-1]!=-1){
				fa[u][i]=fa[fa[u][i-1]][i-1];
			}
		}
		for(int e=head[u];e!=-1;e=edge[e].next){
			int v=edge[e].v;
			if(dep[v]==0){
				dep[v]=dep[u]+1;
				fa[v][0]=u;
				que.push(v);
			}
		}
	}
}

int LCA(int u,int v){
	int i,j;
	if(dep[u]<dep[v])swap(u,v);
	for(i=0;(1<<i)<=dep[u];i++);
	i--;
	for(j=i;j>=0;j--){
		if(dep[u]-(1<<j)>=dep[v])
		u=fa[u][j];
	}
	if(u==v) return u;
	for(j=i;j>=0;j--){
		if(fa[u][j]!=-1&&fa[u][j]!=fa[v][j]){
			u=fa[u][j];
			v=fa[v][j];
		}
	}
	return fa[u][0];
}

int main(){
	int n,q,u,v;
	while(scanf("%d",&n)!=EOF){
		memset(head,-1,sizeof(head));
		tot=0;
		memset(fa,-1,sizeof(fa));
		for(int i=1;i<n;i++){
			scanf("%d%d",&u,&v);
			addedge(u,v);
			addedge(v,u);
		}
		memset(dep,0,sizeof(dep));
		BFS(1);
	//	cout<<LCA(1,5)<<endl;
	//	cout<<LCA(2,3)<<endl;
		for(int i=1;i<=n;i++)
		dp[i][0]=i;
		for(int j=1;j<=20;j++){
			for(int i=1;i+(1<<j)-1<=n;i++)
			dp[i][j]=LCA(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
		}
/*		for(int i=1;i<=n;i++){
			for(int j=0;j<=6;j++)
			cout<<dp[i][j]<<" ";
			cout<<endl;
		}*/
		scanf("%d",&q);
		while(q--){
			scanf("%d%d",&u,&v);
	//		if(u>v) swap(u,v);
			if(u==v)
			printf("%d\n",u);
			else{
				int ans=u;
				for(int i=20;i>=0;i--){
					if(u+(1<<i)-1<=v){
						ans=LCA(ans,dp[u][i]);
						u=u+(1<<i);
					}
				}
				printf("%d\n",ans);
			}
		}
	}
	return 0;
}

  

时间: 2024-11-03 20:16:08

HDU 5266 bc# 43 LCA+跳表的相关文章

hdu 5266 pog loves szh III 在线lca+线段树区间优化

题目链接:hdu 5266 pog loves szh III 思路:因为它查询的是区间上的lca,所以我们需要用在线lca来处理,达到单点查询的复杂度为O(1),所以我们在建立线段树区间查询的时候可以达到O(1*nlgn)的时间复杂度 ps:因为栈很容易爆,所以.....你懂的 -->#pragma comment(linker, "/STACK:1024000000,1024000000") /*****************************************

HDU 5266 pog loves szh III (线段树+在线LCA转RMQ)

题目地址:HDU 5266 这题用转RMQ求LCA的方法来做的非常简单,只需要找到l-r区间内的dfs序最大的和最小的就可以,那么用线段树或者RMQ维护一下区间最值就可以了.然后就是找dfs序最大的点和dfs序最小的点的最近公共祖先了. 代码如下: #include <iostream> #include <string.h> #include <math.h> #include <queue> #include <algorithm> #inc

bc #43(hdu 5265) pog loves szh II

pog loves szh II Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 2115    Accepted Submission(s): 609 Problem Description Pog and Szh are playing games.There is a sequence with n numbers, Pog wil

HDU 4822 Tri-war(LCA树上倍增)(2013 Asia Regional Changchun)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4822 Problem Description Three countries, Red, Yellow, and Blue are in war. The map of battlefield is a tree, which means that there are N nodes and (N – 1) edges that connect all the nodes. Each country

HDU 5266 pog loves szh III (LAC)

问题描述 pog在与szh玩游戏,首先pog在纸上画了一棵有根树,这里我们定义1为这棵树的根,然后szh在这棵树中选了若干个点,想让pog帮忙找找这些点的最近公共祖先在哪里,一个点为S的最近公共祖先当且仅当以该点为根的子树包含S中的所有点,且该点深度最大.然而,这个问题是十分困难的,出于szh对pog的爱,他决定只找编号连续的点,即l i   ~r i   . 输入描述 若干组数据(不超过3  组n≥10000  或Q≥10000  ). 每组数据第一行一个整数n(1≤n≤300000)  ,表

数据结构:跳表

1.理想情况 在一个使用有序链表描述的具有n个元素的字典中进行搜索,至多需要n次比较.如果在链中部节点加一个指针,则比较次数可以减少到n/2+1.搜索时,首先将要搜索的元素与中间节点进行比较,如果该元素较小,则仅需搜索链表的左半部分.否则,只需搜索又半部分. 以上图为例,如果要搜索的数为26,则将26先与40比较,因为26<40,因此只需要搜索40的左边元素. 而如果在左半部分和右半部分再增加一个中间指针,则可以进一步减小搜索范围(b). 初始的链称为0级链,如上图中的全部节点. 至少指向2个节

SkipList 跳表

为什么选择跳表 目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等. 想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树 出来吗? 很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树, 还要参考网上的代码,相当麻烦. 用跳表吧,跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它, 它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表, 就能轻

Go语言实现跳表(SkipList)

跳表(skiplist)在redis/levelDB中属于核心数据结构,我简单粗暴的用Golang实现了下. 就我的简单理解来说,就一个普通的链表,在insert时,通过Random_level(),把一层变成很多层, 越上数据越小,跨度越大. 查找时从上往下找,用空间换时间. 记下测试代码: package main import ( "fmt" //"github.com/xclpkg/algorithm" "math/rand" ) fun

hdu 5351 MZL&#39;s Border 打表+高精度

MZL's Border Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 462    Accepted Submission(s): 127 Problem Description As is known to all, MZL is an extraordinarily lovely girl. One day, MZL was pl