#2020寒假集训#最近公共祖先入门(Least Common Ancestors)代码笔记

倍增算法(在线:输入一个查询一个)

【存图】

  • 链式前向星(结构体存起始位置、边权等信息)
  • vector邻接表(结构体存终点、边权等信息,下标是起点)

【函数】(样例使用链式前向星存图)

  • 初始化函数(init函数)
    对链式前向星、输入数组、计数变量、标记数组、祖先结点数组初始化
  • 链式前向星函数(EDGE结构体及其内部赋值函数+addEdge函数)
    构造链式前向星,输入的时候直接用addEdge函数加边存储,后续用于遍历
  • 深度搜索函数(DFS函数)
    预处理出每个结点的深度 and 每个结点上一层结点(父结点)
  • LCA初始化函数(LCAinit函数)
    初始化储存每个结点向上 2 的 i 次方层祖先结点,为LCA查询备用
    思想:它的爷爷是它爹的爹
  • LCA查询函数(LCA函数)
    先将深度大的结点移到与另一结点同深度的位置,再同时向上移层,直到它们的父亲结点相同
**初始化函数**
void init()
{
	memset(head,-1,sizeof(head));
	memset(dis,0,sizeof(dis));
	edgecount=-1;
	for(int i=1;i<=n;++i) vis[i]=yes[i]=false,father[i][0]=i;
}
**链式前向星函数**
struct EDGE
{
	int from,to,next;
	long long w;
/*
	Edge(){}是个用来给变量初始化0的函数
	fzhead:fzbody{}为结构体赋初值
*/
	EDGE(){}//后面别加分号 
	fzhead:fzbody{}//后面别加分号 
}edge[maxn];
/*
	i:边的编号;从from到to;w:权值,此处已删除;next:下一条边的编号
	head[i]:由i出发的第一条边的编号
	仅插入一条从u1开始的边,则next是-1,head[u1]为该边的编号
	再读入u1开始的边,则其为第一条边,next是上一条边的编号 
*/
void addEdge(int from,int to,int w)//链序和读入顺序相反 
{
	edge[++edgecount]=EDGE(from,to,w,head[from]);
	head[from]=edgecount;
}
**深度搜索函数**
void DFS(int root, int d)//处理每个结点的深度 and 寻找每个点的父结点 
{//d是初始根结点的上一层深度,因为有deep[root]=++d;赋值前根结点深度也加了1,初始深度一般算作1 
	vis[root]=true;
	deep[root]=++d;//深度递增 
	for(int i=head[root];i!=-1;i=edge[i].next)//递归出口就是到达叶子结点 
	{//链式前向星遍历,遍历的是root的子结点们,子结点的子结点在递归中遍历 
		if(vis[edge[i].to]==false)
		{//无向图 1 到 2 一条边,还有 2 到 1 一条边,不能重复访问,有向图不会出现 
			father[edge[i].to][0]=root;//子孙节点往上2的0次方层(1层)的祖先为父结点 
			dis[edge[i].to]=dis[root]+edge[i].w;
			DFS(edge[i].to,d);
		}//上文的++d先自加后赋值可以使得到此的d就是这层深度,也是对下一层而言的上一层深度 
	}
}
**LCA初始化函数**
void LCAinit()
{
	for(int j=1;j<=18;++j)
	{
		for(int i=1;i<=n;++i)
		{//思想:它的爷爷是它爹的爹 
			father[i][j]=father[father[i][j-1]][j-1];
			/*
				i 往上 2 的 j 次方层的祖先
				就是 i 往上 2 的 j-1 次方层
				再往上 2 的 j-1 次方层的祖先 
				显然 2 的 j 次方 等于 2 的 j-1 次方 *2(两次 j-1 次方)
				每一个点往上 2 的 0 次方层的祖先已经在DFS函数内实现
				现在从 1 次方开始,每个结点的 1 次方先实现,再实现每个结点 2 次方
				以此类推,所以次方数的 j 在外层,内层是所有结点
				每一个次方数都得在所有结点实现后,再进行下一个次方级别的赋值 
			*/
		}
	}
}
**LCA查询函数**
int LCA(int x,int y)//传入询问的两个结点 
{
	if(deep[x]<deep[y]) swap(x,y);//保证询问时 x 的深度比 y 的深度大 
	for(int i=18;i>=0;--i)//循环的目的是让 x 和 y 处于同深度 
	{//18只是一个初始化的习惯性数值,2的18次方层的数结点已经够多了 
		if(deep[father[x][i]]>=deep[y]) x=father[x][i];
		/*
			从深层开始找 
			如果 x 往上 2 的 i 次方层的祖先的深度,大于等于 y 的深度的话
			x 就移动到它的 2 的 i 次方深度的祖先
			从满足深度的大幅度开始调整
			2 的 n 次方……16 8 4 2 1选择移动或不移动,可以实现 2 的 n 次方-1所有层数的移动 
		*/
	}
	//现在 x 和 y 处于同一层(同一深度)啦 
	if(x==y) return x;//令 x 与 y 同深度时,如果 x 和 y 相等,那么这就是LCA的答案,否则继续 
	for(int i=18;i>=0;--i)// x 和 y 一起往上移动 
	{
		if(father[x][i]!=father[y][i])
		{//此次向上 2 的 i 次方层,找到的祖先不一致,x 和 y 都移动到找到的祖先位置(赋值给 x y) 
			x=father[x][i];
			y=father[y][i];
		}
		/*
			如果发现往上移动 n 层祖先一致了,那就把 i 自减接着遍历,用更少的层数往上移动
			直到最后i==0的时候,只移动一层,father就相等,这个时候循环彻底结束,输出x和y的父结点就行
			此时的 x 和 y 已经都被移动到LCA答案结点的子结点位置 
		*/
	}
	return father[x][0] ;
}

图解算法(LCA查询部分:移到同层+移到同父)

#2020寒假集训#最近公共祖先入门(Least Common Ancestors)代码笔记_第1张图片

例题 HDU-2586-How far away ?

Description
There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this “How far is it if I want to go from house A to house B”? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path(“simple” means you can’t visit a place twice) between every two houses. Yout task is to answer all these curious people.

Input
First line is a single integer T(T<=10), indicating the number of test cases.
For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0 Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.

Output
For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.

Sample Input
2
3 2
1 2 10
3 1 15
1 2
2 3
2 2
1 2 100
1 2
2 1

Sample Output
10
25
100
100

AC代码(模板详见代码行注释)

#include
#include
#include 
#include 
#include
#include
#define fzhead EDGE(int _from,int _to,int _w,int _next)
#define fzbody from(_from),to(_to),w(_w),next(_next)
using namespace std;
const int maxn=1e5+10;
int T,n,m,x,y;
int head[maxn],edgecount,father[maxn][20],deep[maxn];
//father数组表示每个点往上2的次方层的祖先 
long long ans,dis[maxn];//dis是从根结点到i结点的权值总路程 
bool vis[maxn],yes[maxn];//vis标记结点是否已被访问,yes用于寻找根结点 
void init()
{
	memset(head,-1,sizeof(head));
	memset(dis,0,sizeof(dis));
	edgecount=-1;
	for(int i=1;i<=n;++i) vis[i]=yes[i]=false,father[i][0]=i;
}
struct EDGE
{
	int from,to,next;
	long long w;
/*
	Edge(){}是个用来给变量初始化0的函数
	fzhead:fzbody{}为结构体赋初值
*/
	EDGE(){}//后面别加分号 
	fzhead:fzbody{}//后面别加分号 
}edge[maxn];
/*
	i:边的编号;从from到to;w:权值,此处已删除;next:下一条边的编号
	head[i]:由i出发的第一条边的编号
	仅插入一条从u1开始的边,则next是-1,head[u1]为该边的编号
	再读入u1开始的边,则其为第一条边,next是上一条边的编号 
*/
void addEdge(int from,int to,int w)//链序和读入顺序相反 
{
	edge[++edgecount]=EDGE(from,to,w,head[from]);
	head[from]=edgecount;
}
void DFS(int root, int d)//处理每个结点的深度 and 寻找每个点的父结点 
{//d是初始根结点的上一层深度,因为有deep[root]=++d;赋值前根结点深度也加了1,初始深度一般算作1 
	vis[root]=true;
	deep[root]=++d;//深度递增 
	for(int i=head[root];i!=-1;i=edge[i].next)//递归出口就是到达叶子结点 
	{//链式前向星遍历,遍历的是root的子结点们,子结点的子结点在递归中遍历 
		if(vis[edge[i].to]==false)
		{//无向图 1 到 2 一条边,还有 2 到 1 一条边,不能重复访问,有向图不会出现 
			father[edge[i].to][0]=root;//子孙节点往上2的0次方层(1层)的祖先为父结点 
			dis[edge[i].to]=dis[root]+edge[i].w;
			DFS(edge[i].to,d);
		}//上文的++d先自加后赋值可以使得到此的d就是这层深度,也是对下一层而言的上一层深度 
	}
}
void LCAinit()
{
	for(int j=1;j<=18;++j)
	{
		for(int i=1;i<=n;++i)
		{//思想:它的爷爷是它爹的爹 
			father[i][j]=father[father[i][j-1]][j-1];
			/*
				i 往上 2 的 j 次方层的祖先
				就是 i 往上 2 的 j-1 次方层
				再往上 2 的 j-1 次方层的祖先 
				显然 2 的 j 次方 等于 2 的 j-1 次方 *2(两次 j-1 次方)
				每一个点往上 2 的 0 次方层的祖先已经在DFS函数内实现
				现在从 1 次方开始,每个结点的 1 次方先实现,再实现每个结点 2 次方
				以此类推,所以次方数的 j 在外层,内层是所有结点
				每一个次方数都得在所有结点实现后,再进行下一个次方级别的赋值 
			*/
		}
	}
}
int LCA(int x,int y)//传入询问的两个结点 
{
	if(deep[x]<deep[y]) swap(x,y);//保证询问时 x 的深度比 y 的深度大 
	for(int i=18;i>=0;--i)//循环的目的是让 x 和 y 处于同深度 
	{//18只是一个初始化的习惯性数值,2的18次方层的数结点已经够多了 
		if(deep[father[x][i]]>=deep[y]) x=father[x][i];
		/*
			从深层开始找 
			如果 x 往上 2 的 i 次方层的祖先的深度,大于等于 y 的深度的话
			x 就移动到它的 2 的 i 次方深度的祖先
			从满足深度的大幅度开始调整
			2 的 n 次方……16 8 4 2 1选择移动或不移动,可以实现 2 的 n 次方-1所有层数的移动 
		*/
	}
	//现在 x 和 y 处于同一层(同一深度)啦 
	if(x==y) return x;//令 x 与 y 同深度时,如果 x 和 y 相等,那么这就是LCA的答案,否则继续 
	for(int i=18;i>=0;--i)// x 和 y 一起往上移动 
	{
		if(father[x][i]!=father[y][i])
		{//此次向上 2 的 i 次方层,找到的祖先不一致,x 和 y 都移动到找到的祖先位置(赋值给 x y) 
			x=father[x][i];
			y=father[y][i];
		}
		/*
			如果发现往上移动 n 层祖先一致了,那就把 i 自减接着遍历,用更少的层数往上移动
			直到最后i==0的时候,只移动一层,father就相等,这个时候循环彻底结束,输出x和y的父结点就行
			此时的 x 和 y 已经都被移动到LCA答案结点的子结点位置 
		*/
	}
	return father[x][0] ;
}
int main()
{
	scanf("%d",&T) ;
	while(T--)
	{
		scanf("%d %d",&n,&m);
		init();
		int from,to;
		long long w;
		for(int i=1;i<=n-1;++i)
		{
			scanf("%d %d %lld",&from,&to,&w);
			addEdge(from,to,w); 
			addEdge(to,from,w);
			yes[to]=true;
		}//初始化edgecount为-1,所以从0号边开始存入,但链式前向星遍历不受影响 
		DFS(1,0);//根结点深度从1开始,先给所有结点找好自己的父结点,然后进行下文循环 
		LCAinit();
		for(int i=1;i<=m;++i)
		{
			scanf("%d %d",&x,&y);//输入需查询的两个结点
			ans=dis[x]+dis[y]-dis[LCA(x,y)]*2;
			printf("%lld\n",ans);
		}
	}
	return 0;
}

Tarjan算法(离线:输入一堆统一查询)

【存图】

  • 链式前向星(结构体存起始位置、边权等信息)
  • vector邻接表(结构体存终点、边权等信息,下标是起点)

【函数】(样例使用链式前向星存图)

  • 初始化函数(init函数)
    对链式前向星、输入数组、计数变量、标记数组、祖先结点数组初始化以及询问vector清空
  • 并查集函数(find函数+baba函数)
    后续用于查找祖先+合并实现祖先传递
  • 链式前向星函数(EDGE结构体及其内部赋值函数+addEdge函数)
    构造链式前向星,输入的时候直接用addEdge函数加边存储,后续用于遍历
  • Tarjan实现LCA查询函数(tarjan函数)
    深度搜索(DFS)遍历访问 and 递归逆向返回进行baba函数合并传递祖先
**初始化函数**
void init()
{
	memset(head,-1,sizeof(head));
	memset(ans,0,sizeof(ans));
	edgecount=-1;
	for(int i=1;i<=n;++i) vis[i]=yes[i]=false,father[i]=i,qst[i].clear();
}
**并查集函数**
int find(int x) {return x==father[x]?x:father[x]=find(father[x]);}
void baba(int x,int y) {int fx=find(x);int fy=find(y);father[fx]=fy;}
**链式前向星函数**
struct EDGE
{
	int from,to,next;
/*
	Edge(){}是个用来给变量初始化0的函数
	fzhead:fzbody{}为结构体赋初值
*/
	EDGE(){}//后面别加分号 
	fzhead:fzbody{}//后面别加分号 
}edge[maxn];
/*
	i:边的编号;从from到to;w:权值,此处已删除;next:下一条边的编号
	head[i]:由i出发的第一条边的编号
	仅插入一条从u1开始的边,则next是-1,head[u1]为该边的编号
	再读入u1开始的边,则其为第一条边,next是上一条边的编号 
*/
void addEdge(int from,int to)//链序和读入顺序相反 
{
	edge[++edgecount]=EDGE(from,to,head[from]);
	head[from]=edgecount;
}
**Tarjan实现LCA查询函数**
void tarjan(int root)
{
	vis[root]=true;//遍历到的根结点标记已被访问 
	for(int i=head[root];i!=-1;i=edge[i].next)//递归出口就是到达叶子结点 
	{//链式前向星遍历,遍历的是root的子结点们,子结点的子结点在递归中遍历 
		if(vis[edge[i].to]==false)
		{//无向图 1 到 2 一条边,还有 2 到 1 一条边,不能重复访问,有向图不会出现 
			tarjan(edge[i].to);//递归中第二个循环也会经历,把当前结点看做根结点 
			baba(edge[i].to,root);//合并到祖宗 
		}
		/*
			合并前提是所有子结点都已返回
			不然退出if还会继续进行i=edge[i].next遍历 
			不足以彻底让子结点们的父结点if内的tarjan结束 
		*/
	}
	for(int i=0;i<qst[root].size();++i)
	{//遍历询问,与root相关的有qst[root].size()个询问 
		if(vis[qst[root][i].first]==true)//注意vector第一维确定,第二维可变长 
		{//qst[root][i]是与root相关的第i个询问,first是相关结点,second是询问序号 
			ans[qst[root][i].second]=find(qst[root][i].first);
		}//若相关结点已经被遍历到,就用相关结点的祖先,作为这个second序号LCA询问的answer 
	}
}

图解算法(LCA查询部分:顺搜逆回)

#2020寒假集训#最近公共祖先入门(Least Common Ancestors)代码笔记_第2张图片

例题 POJ-1330-Nearest Common Ancestors

Description
A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:
#2020寒假集训#最近公共祖先入门(Least Common Ancestors)代码笔记_第3张图片
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

AC代码(模板详见代码行注释)

#include
#include
#include 
#include 
#include
#include
#define fzhead EDGE(int _from,int _to,int _next)
#define fzbody from(_from),to(_to),next(_next)
using namespace std;
const int maxn=1e4+10;
int head[maxn],edgecount,father[maxn],ans[maxn];
bool vis[maxn],yes[maxn];//vis标记结点是否已被访问,yes用于寻找根结点 
int T,n,m,s,cnt=0;
vector<pair<int,int> > qst[maxn];
//下标是询问结点,first是相关结点,second是询问序号 
void init()
{
	memset(head,-1,sizeof(head));
	memset(ans,0,sizeof(ans));
	edgecount=-1;
	for(int i=1;i<=n;++i) vis[i]=yes[i]=false,father[i]=i,qst[i].clear();
}
int find(int x) {return x==father[x]?x:father[x]=find(father[x]);}
void baba(int x,int y) {int fx=find(x);int fy=find(y);father[fx]=fy;}
struct EDGE
{
	int from,to,next;
/*
	Edge(){}是个用来给变量初始化0的函数
	fzhead:fzbody{}为结构体赋初值
*/
	EDGE(){}//后面别加分号 
	fzhead:fzbody{}//后面别加分号 
}edge[maxn];
/*
	i:边的编号;从from到to;w:权值,此处已删除;next:下一条边的编号
	head[i]:由i出发的第一条边的编号
	仅插入一条从u1开始的边,则next是-1,head[u1]为该边的编号
	再读入u1开始的边,则其为第一条边,next是上一条边的编号 
*/
void addEdge(int from,int to)//链序和读入顺序相反 
{
	edge[++edgecount]=EDGE(from,to,head[from]);
	head[from]=edgecount;
}
void tarjan(int root)
{
	vis[root]=true;//遍历到的根结点标记已被访问 
	for(int i=head[root];i!=-1;i=edge[i].next)//递归出口就是到达叶子结点 
	{//链式前向星遍历,遍历的是root的子结点们,子结点的子结点在递归中遍历 
		if(vis[edge[i].to]==false)
		{//无向图 1 到 2 一条边,还有 2 到 1 一条边,不能重复访问,有向图不会出现 
			tarjan(edge[i].to);//递归中第二个循环也会经历,把当前结点看做根结点 
			baba(edge[i].to,root);//合并到祖宗 
		}
		/*
			合并前提是所有子结点都已返回
			不然退出if还会继续进行i=edge[i].next遍历 
			不足以彻底让子结点们的父结点if内的tarjan结束 
		*/
	}
	for(int i=0;i<qst[root].size();++i)
	{//遍历询问,与root相关的有qst[root].size()个询问 
		if(vis[qst[root][i].first]==true)//注意vector第一维确定,第二维可变长 
		{//qst[root][i]是与root相关的第i个询问,first是相关结点,second是询问序号 
			ans[qst[root][i].second]=find(qst[root][i].first);
		}//若相关结点已经被遍历到,就用相关结点的祖先,作为这个second序号LCA询问的answer 
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);//一共n个结点,n-1条边 
		init();//初始化用到了n,一定要在输入后再初始化鸭!!! 
		for(int i=1,from,to;i<=n-1;++i)
		{
			scanf("%d %d",&from,&to);
			addEdge(from,to); 
			yes[to]=true;
		}//初始化edgecount为-1,所以从0号边开始存入,但链式前向星遍历不受影响 
		int root;
		for(int i=1;i<=n;++i)
		{//所有的to都成了true,最后是false的只有唯一一个from 
			if(yes[i]==false)
			{
				root=i;
				break;
			}
		}
		m=1;//询问个数,此处仅出入一例 
		int putfrom,putto;
		scanf("%d %d",&putfrom,&putto);
		//对于1号询问,putfrom和putto都可能是询问结点,另一个为相关结点 
		qst[putfrom].push_back(make_pair(putto,1)); 
		qst[putto].push_back(make_pair(putfrom,1)); 
		//vector第二维下标自动从0开始存,所以函数内部第二个遍历i=0开始 
		tarjan(root);//传入根结点序号
		//for(int i=1;i<=m;++i) printf("%d\n",ans[i]);此处仅1例
		printf("%d\n",ans[1]);
	}
	return 0;
}

你可能感兴趣的:(2020寒假集训,算法)