• [洛谷P3379]【模板】最近公共祖先(LCA)


    题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

    题解:有多种做法

    倍增:(写于2017-9-13)

    bfs函数:处理每个节点的深度(其实dfs更常用,当时我用的是bfs)和父亲(dad[i][0])

    init函数:处理每个节点的二的整数幂的父亲,dad[i][j]存的是第i个节点的第$2^{j}$个祖先,dad[i][0]就是它的父亲,没有就是0。

    可发现每个节点的第$2^{j}$个祖先是它的第$2^{j-1}$个祖先的$2^{j-1}$个祖先,也就是dad[i][j]=dad[dad[i][j-1]][j-1]($2^{n}$=$2^{n-1}$+$2^{n-1}$)。要特别注意一定要把关于j的循环放在外面,因为是一层一层处理的

    LCA函数:求LCA,具体见程序中

    C++ Code:

    #include<cstdio>
    #define maxn 500100
    struct Edge{
    	int next,to;
    }edge[maxn<<1];
    int dep[maxn],dad[maxn][20];
    int head[maxn],cnt;
    int q[maxn],v[maxn],t=0,w;
    int n,m,root;
    void swap(int &a,int &b){a^=b;b^=a;a^=b;}
    void add(int a,int b){
    	edge[++cnt].to=b;
    	edge[cnt].next=head[a];
    	head[a]=cnt;
    }
    void bfs(){
    	v[q[w=1]=root]=dep[root]=1;
    	while (t<w){
    		int x=q[++t];
    		for (int i=head[x];i;i=edge[i].next){
    			int ne=edge[i].to;
    			if (!v[ne]){
    				v[q[++w]=ne]=1;
    				dep[ne]=dep[dad[ne][0]=x]+1;	
    			}
    		}
    	}
    }
    void init(){
    	for (int j=1;j<20;j++){
    		for (int i=1;i<=n;i++){
    			dad[i][j]=dad[dad[i][j-1]][j-1];
    		}
    	}
    }
    int LCA(int x,int y){
    	if (dep[x]<dep[y])swap(x,y);//保证x的深度大于y 
    	int temp6=dep[x]-dep[y];//让x的深度与y相同,可以使用二进制的思想,不过一般一个循环搞定(见下方注释处)
    	for (int i=0;i<20;i++){
    		if (temp6 & (1<<i))x=dad[x][i];
    	}
    	/*更常用的方法: 
    	for (int i=19;i>=0;i--)if (dep[dad[x][i]]>=dep[y])x=dad[x][i];
    	*/ 
    	if (x==y)return x;//如果两个已经相同就返回其中一个 
    	for (int i=19;i>=0;i--){//只要dad[x][i]!=dad[y][i],两个节点一起向上跳(如果相同表示跳多了) 
    		if (dad[x][i]!=dad[y][i]){
    			x=dad[x][i];
    			y=dad[y][i];
    		}
    	}
    	return dad[x][0];//因为不相同,所以再向上跳一个 
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&root);
    	for (int i=1;i<n;i++){
    		int a,b;
    		scanf("%d%d",&a,&b);
    		add(a,b);
    		add(b,a);
    	}
    	bfs();
    	init();
    	for (int i=0;i<m;i++){
    		int a,b;
    		scanf("%d%d",&a,&b);
    		printf("%d
    ",LCA(a,b));
    	}
    	return 0;
    }
    

     树链剖分:(写于2017-12-15)

    dfs1,dfs2函数:树链剖分,见树链剖分

    LCA:求LCA,这一部分很简单,如果两个节点不在同一条重链上,就把其中重链顶部深度深的那个节点向上跳到其所在重链顶部的父亲。直到两个节点在同一条重链上,就返回深度较浅的一个

    C++ Code:

    #include<cstdio>
    using namespace std;
    const int maxn=500100;
    int n,m,r,idx;
    int dep[maxn],fa[maxn],top[maxn],dfn[maxn],siz[maxn],son[maxn];
    int head[maxn],cnt;
    struct Edge{
    	int to,nxt;
    }e[maxn<<1];
    void swap(int &a,int &b){a^=b^=a^=b;}
    void addE(int a,int b){
    	e[++cnt]=(Edge){b,head[a]};
    	head[a]=cnt;
    }
    void dfs1(int rt){
    	siz[rt]=1;
    	for (int i=head[rt];i;i=e[i].nxt){
    		int ne=e[i].to;
    		if (ne!=fa[rt]){
    			fa[ne]=rt;
    			dep[ne]=dep[rt]+1;
    			dfs1(ne);
    			if (son[rt]==0||siz[ne]>siz[son[rt]])son[rt]=ne;
    			siz[rt]+=siz[ne];
    		}
    	}
    }
    void dfs2(int rt){
    	dfn[rt]=++idx;
    	int ne=son[rt];
    	if (ne)top[ne]=top[rt],dfs2(ne);
    	for (int i=head[rt];i;i=e[i].nxt){
    		ne=e[i].to;
    		if (ne==son[rt]||ne==fa[rt])continue;
    		top[ne]=ne;
    		dfs2(ne);
    	}
    }
    int LCA(int x,int y){
    	while (top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]])swap(x,y);
    		x=fa[top[x]];
    	}
    	return dep[x]<dep[y]?x:y;
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&r);
    	for (int i=1;i<n;i++){
    		int a,b;
    		scanf("%d%d",&a,&b);
    		addE(a,b),addE(b,a);
    	}
    	dep[top[r]=r]=1;
    	dfs1(r);
    	dfs2(r);
    	while (m--){
    		int a,b;
    		scanf("%d%d",&a,&b);
    		printf("%d
    ",LCA(a,b));
    	}
    	return 0;
    }
    

    tarjan:(写于2018-8-5)

    把询问离线,一次$dfs$即可,$O(n + m) $(有没有$alpha$啊?)

    C++ Code:

    #include <cstdio>
    #define maxn 500100
    using namespace std;
    int n, m, root, x, y;
    int vis[maxn], f[maxn], ans[maxn];
    int head[maxn], cnt;
    struct Edge {
    	int to, nxt;
    } e[maxn << 1];
    int headq[maxn], cntq;
    struct Query {
    	int oth, nxt, tg;
    } Q[maxn << 1];
    void addE(int a, int b) {
    	e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt;
    }
    void add(int a, int b, int c) {
    	Q[++cntq] = (Query) {b, headq[a], c}; headq[a] = cntq;
    }
    int find(int x) {return ((x == f[x]) ? x : (f[x] = find(f[x])));}
    void dfs(int rt) {
    	vis[rt] = true;
    	int v;
    	for (int i = head[rt]; i; i = e[i].nxt) {
    		v = e[i].to;
    		if (!vis[v]) {
    			dfs(v);
    			f[v] = find(rt);
    		}
    	}
    	for (int i = headq[rt]; i; i = Q[i].nxt) ans[Q[i].tg] = find(Q[i].oth);
    }
    int main() {
    	scanf("%d%d%d", &n, &m, &root);
    	for (int i = 1; i < n; i++) {
    		f[i] = i;
    		scanf("%d%d", &x, &y);
    		addE(x, y);
    		addE(y, x);
    	}
    	f[n] = n;
    	for (int i = 0; i < m; i++) {
    		scanf("%d%d", &x, &y);
    		add(x, y, i);
    		add(y, x, i);
    	}
    	dfs(root);
    	for (int i = 0; i < m; i++) {
    		printf("%d
    ", ans[i]);
    	}
    }
    

      

    RMQ:(写于2018-11-26)

    $ST$表维护$RMQ$,先$dfs$,每次经过一个点就把这个点加入一个数组(可以证明,数组长度为$2n-1$),记$pos_i$为第$i$个点第一次在数组中出现的位置。求$x,y$的$LCA$,就是求区间$[pos_x,pos_y]$中深度最浅的点。$ST$表维护即可

    卡点:$lg$数组没开两倍

    C++ Code:

    #include <cstdio>
    #include <algorithm>
    #include <cctype>
    namespace __IO {
    	namespace R {
    		int x, ch;
    		inline int read() {
    			ch = getchar();
    			while (isspace(ch)) ch = getchar();
    			for (x = ch & 15, ch = getchar(); isdigit(ch); ch = getchar()) x = x * 10 + (ch & 15);
    			return x;
    		}
    	}
    }
    using __IO::R::read;
    
    #define maxn 500010
    int head[maxn], cnt;
    struct Edge {
    	int to, nxt;
    } e[maxn << 1];
    inline void addedge(int a, int b) {
    	e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt;
    	e[++cnt] = (Edge) {a, head[b]}; head[b] = cnt;
    }
    
    int n, m, rt;
    int LG[maxn << 1];
    #define M 20
    int ST[M][maxn << 1], dep[maxn], pos[maxn], idx;
    void dfs(int u, int fa = 0) {
    	ST[0][++idx] = u, pos[u] = idx;
    	for (int i = head[u]; i; i = e[i].nxt) {
    		int v = e[i].to;
    		if (v != fa) {
    			dep[v] = dep[u] + 1;
    			dfs(v, u);
    			ST[0][++idx] = u;
    		}
    	}
    }
    
    inline int getmin(int a, int b) {return dep[a] < dep[b] ? a : b;}
    inline int LCA(int x, int y) {
    	int l = pos[x], r = pos[y];
    	if (l > r) std::swap(l, r);
    	int tmp = LG[r - l + 1];
    	return getmin(ST[tmp][l], ST[tmp][r - (1 << tmp) + 1]);
    }
    
    int main() {
    	n = read(), m = read(), rt = read();
    	for (int i = 1, a, b; i < n; i++) {
    		a = read(), b = read();
    		addedge(a, b);
    	}
    	dfs(rt);
    	for (int i = 1; i < 20; i++) {
    		for (int j = 1; j + (1 << i) - 1 <= idx; j++) {
    			ST[i][j] = getmin(ST[i - 1][j], ST[i - 1][j + (1 << i - 1)]);
    		}
    	}
    	LG[0] = -1; for (int i = 1; i <= idx; i++) LG[i] = LG[i >> 1] + 1;
    	while (m --> 0) {
    		int x = read(), y = read();
    		printf("%d
    ", LCA(x, y));
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    day28-描述符应用与类的装饰器
    MySQL-快速入门(8)存储过程、存储函数
    MySQL-快速入门(7)索引
    MySQL-快速入门(6)连接查询、子查询、正则表达式查询、数据的插入删除更新
    MySQL-快速入门(5)数据查询-常用关键字、分组查询、聚合函数
    MySQL-快速入门(4)MySQL函数
    MySQL-快速入门(3)运算符
    MySQL-快速入门(2)数据类型
    MySQL-快速入门(1)基本数据库、表操作语句
    MySql-Mysql技术内幕~SQL编程学习笔记(N)
  • 原文地址:https://www.cnblogs.com/Memory-of-winter/p/8046320.html
Copyright © 2020-2023  润新知