LCA问题

LCA(Least Common Ancestors),即最近公共祖先:在有根树中,找出某两个结点u和v最近的公共祖先(另一种说法,离树根最远的公共祖先)。 

      使用RMQ解决LCA问题,一篇不错的文档http://wenku.baidu.com/view/392c7023dd36a32d73758193.html,解释了两者之间的转换。

在线算法DFS+ST描述(思想是:将树看成一个无向图,u和v的公共祖先一定在u与v之间的最短路径上):

(1)DFS:从树T的根开始,进行深度优先遍历(将树T看成一个无向图),并记录下每次到达的顶点。第一个的结点是root(T),每经过一条边都记录它的端点。由于每条边恰好经过2次,因此一共记录了2n-1个结点,用E[1, ... , 2n-1]来表示。

(2)计算R:用R[i]表示E数组中第一个值为i的元素下标,即如果R[u] < R[v]时,DFS访问的顺序是E[R[u], R[u]+1, …, R[v]]。虽然其中包含u的后代,但深度最小的还是u与v的公共祖先。

(3)RMQ:当R[u] ≥ R[v]时,LCA[u, v] = RMQ(R[v], R[u]);否则LCA[u, v] = RMQ( R[u], R[v]).

利用树的性质,如果边(a,b)是父子边,即a为b在搜索树中的父亲,则dis[b]=dis[a]+weght[a][b];(dis为某点到根节点的距离)因此树上任意两点间距离=dis[a]+dis[b]-2*dis[lca(a,b)];

2.离线算法Tarjan解决LCA问题

下面是算法的详细讲解:
  首先,Tarjan算法是一种离线算法,也就是说,它要首先读入所有的询问(求一次LCA叫做一次询问),然后并不一定按照原来的顺序处理这些询问。而打乱这个顺序正是这个算法的巧妙之处。看完下文,你便会发现,如果偏要按原来的顺序处理询问,Tarjan算法将无法进行。
  Tarjan算法是利用并查集来实现的。它按DFS的顺序遍历整棵树。对于每个结点x,它进行以下几步操作:

  • 计算当前结点的层号lv[x],并在并查集中建立仅包含x结点的集合,即root[x]:=x。
  • 依次处理与该结点关联的询问。
  • 递归处理x的所有孩子。
  • root[x]:=root[father[x]](对于根结点来说,它的父结点可以任选一个,反正这是最后一步操作了)。
  现在我们来观察正在处理与x结点关联的询问时并查集的情况。由于一个结点处理完毕后,它就被归到其父结点所在的集合,所以在已经处理过的结点中(包括x本身),x结点本身构成了与x的LCA是x的集合,x结点的父结点及以x的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[x]的集合,x结点的父结点的父结点及以x的父结点的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[father[x]]的集合……(上面这几句话如果看着别扭,就分析一下句子成分,也可参照右面的图)假设有一个询问(x,y)(y是已处理的结点),在并查集中查到y所属集合的根是z,那么z就是x和y的LCA,x到y的路径长度就是lv[x]+lv[y]-lv[z]*2。累加所有经过的路径长度就得到答案。
  现在还有一个问题:上面提到的询问(x,y)中,y是已处理过的结点。那么,如果y尚未处理怎么办?其实很简单,只要在询问列表中加入两个询问(x,y)、(y,x),那么就可以保证这两个询问有且仅有一个被处理了(暂时无法处理的那个就pass掉)。而形如(x,x)的询问则根本不必存储。
  如果在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将可以认为是常数级的,整个算法也就是线性的了。


poj1330和poj 1986都是基础的LCA问题,用于测模板的,注意给出的边是单向还是双向。

1.转RMQ做法

class LCA {
		int rmq[];// 1~n
		int n, len, E[];// 图
		node buf[];// 边
		int F[], pos[], cnt;// 欧拉序列(第i次遍历到的元素),元素第一次遍历到的cnt;posmn[]元素最后一次遍历到的cnt。则{pos[i],posmn[i]}段代表i的子树
		int dis[];// 各点到根节点距离
		RMQ st;
		LCA(int maxn) {
			buf = new node[maxn * 2];
			E = new int[maxn];
			F = new int[maxn * 2];
			rmq = new int[maxn * 2];
			pos = new int[maxn];
			st = new RMQ(maxn * 2);
			dis = new int[maxn];
		}
		void init(int n) {
			this.n = n;
			len = 0;
			Arrays.fill(E, -1);
		}
		void addedge(int a, int b, int w) {
			buf[len] = new node(b, E[a], w);
			E[a] = len++;
			buf[len] = new node(a, E[b], w);
			E[b] = len++;
		}

		int query(int a, int b) {
			int k = st.getmin(pos[a], pos[b]);
			return dis[a] + dis[b] - 2 * dis[F[k]]; 
		}
		void dfs(int a, int lev) {
			cnt++;
			F[cnt] = a;
			rmq[cnt] = lev;
			pos[a] = cnt;
			for (int i = E[a]; i != -1; i = buf[i].ne) {
				int b = buf[i].be;
				if (pos[b] != -1)
					continue;
				dis[b] = dis[a] + buf[i].weight;
				dfs(b, lev + 1);
				cnt++;
				F[cnt] = a;
				rmq[cnt] = lev;
			}
		}
		void solve(int root) {
			cnt = 0;
			Arrays.fill(pos, -1);
			Arrays.fill(dis, 0);
			dfs(root, 0);
			st.init(2 * n - 1);
		}
	}

2.Tarjan做法

void dfs(int a, int p) {
		vis[a] = 1;
		for (int i = Eb[a]; i != -1; i = buf[i].ne) {
			int b = buf[i].be;
			if (vis[b] == 0) {
				dis[b] = dis[a] + buf[i].val;
				dfs(b, a);
			}
		}
		for (int i = Eq[a]; i != -1; i = query[i].ne) {
			int b = query[i].be;
			if (vis[b] == 1){
				int temp=find(b);
				ans[query[i].val] = dis[a]+dis[b]-2*dis[temp];
				}
		}
		f[a] = find(p);
	}



你可能感兴趣的:(c,算法,query,文档)