题意:求树上两个节点的最近公共祖先
算法一:tarjan
LCA(u) {
Make-Set(u)
ancestor[Find-Set(u)]=u //设置u所在集合的祖先
对于u的每一个孩子v {
LCA(v)
Union(v,u) //把v生成的子集并入u中
ancestor[Find-Set(u)]=u //防止采用树形启发式合并使u的集合根(代表)变化
}
checked[u]=true
对于每个(u,v)属于P {
if checked[v]=true
then 回答u和v的最近公共祖先为 ancestor[Find-Set(v)]
}
}
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<vector> #include<queue> using namespace std; /* 时间复杂度O(N+Q) 离线算法,必须先记录询问 对于每一对询问lac(u,v),在u,v的查询队列里各加一次 */ const int MAXN = 10010; class LCA_Tarjan { public: int n, father[MAXN]; bool vis[MAXN]; vector<int> edge[MAXN]; vector<int> query[MAXN]; void init(int n) { for(int i = 1; i <= n; i++) { vis[i] = false; edge[i].clear(); query[i].clear(); } make_set(n); } void make_set(int n) //下标从1开始 { for(int i = 1; i <= n; i++) father[i] = i; } int find(int u) { if(u == father[u]) return u; return father[u] = find(father[u]); } void Union(int u, int v) //可以优化 { int fu = find(u); int fv = find(v); if(fv != fu) father[fv] = fu; } void tarjan(int u) { father[u] = u; int sz = edge[u].size(); for(int i = 0; i < sz; i++) { tarjan(edge[u][i]); Union(u, edge[u][i]); father[find(u)] = u; } vis[u] = true; sz = query[u].size(); for(int i = 0; i < sz; i++) { if(vis[query[u][i]] == true) cout << father[find(query[u][i])] << endl; } } }; int main() { LCA_Tarjan t; int T; scanf("%d",&T); while(T--) { scanf("%d", &t.n); t.init(t.n); int u, v; bool notRoot[MAXN] = {0}; for(int i = 1; i < t.n; i++) { scanf("%d %d", &u, &v); t.edge[u].push_back(v); notRoot[v] = true; } scanf("%d %d", &u, &v); t.query[u].push_back(v); t.query[v].push_back(u); for(int i = 1; i <= t.n; i++) if(notRoot[i] == false) {t.tarjan(i); break;} } return 0; }
算法二LCA向RMQ的转化:
/* 1.时间复杂度O(N*logN+Q) 2.在线算法 3.搜索之后n个节点得到2*n-1个编号 */ #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<vector> using namespace std; const int MAXN = 10010; const int PP = 25; class LCA_RMQ { public: int n; int pow2[PP]; //2^k int tim; //时间戳,从1开始 int first[MAXN]; //节点第一次访问的时间戳 int nodeId[MAXN*2]; //与相应时间戳对应的节点编号 int dep[MAXN*2]; //深度 int dp[MAXN*2][PP]; //搜索之后得到2*n-1个编号 bool vis[MAXN]; //记录节点是否被访问过 vector<int> edge[MAXN]; void init(int n) { for(int i = 0; i < PP; i++) pow2[i] = (1<<i); for(int i = 1; i <= n; i++) { edge[i].clear(); vis[i] = false; } } void addedge(int u ,int v) { edge[u].push_back(v); } void dfs(int u ,int d) //u是节点, d是深度 { tim++; //时间戳+1 vis[u] = true; nodeId[tim] = u; //记录与时间戳相对应的节点 first[u] = tim; //记录下节点u首次访问的时间戳 dep[tim] = d; //根节点的深度为1 int sz = edge[u].size(); for(int i = 0; i < sz; i++) { int v = edge[u][i]; if(vis[v] == false) { dfs(v, d + 1); tim++; nodeId[tim] = u; //只要访问的不是叶子节点,tim都要重复增加 dep[tim] = d; } } } void ST(int len) { int k = (int)(log(len+1.0) / log(2.0)); for(int i = 1; i <= len; i++) dp[i][0] = i; for(int j = 1; j <= k; j++) for(int i = 1; i + pow2[j] - 1 <= len; i++) { int a = dp[i][j-1]; //a, b均为下标 int b = dp[i+pow2[j-1]][j-1]; if(dep[a] < dep[b]) dp[i][j] = a; else dp[i][j] = b; } } int RMQ(int x ,int y) { int k = (int)(log(y-x+1.0) / log(2.0)); int a = dp[x][k]; int b = dp[y-pow2[k]+1][k]; if(dep[a] < dep[b]) return a; else return b; } int LCA(int u ,int v) { int x = first[u]; int y = first[v]; if(x > y) swap(x,y); int index = RMQ(x,y); return nodeId[index]; } }; LCA_RMQ t; int main() { int T; scanf("%d",&T); while(T--) { int u, v, rt; bool notRoot[MAXN] = {0}; scanf("%d",&t.n); t.init(t.n); for(int i = 1; i < t.n; i++) { scanf("%d %d",&u,&v); t.addedge(u, v); notRoot[v] = true; } scanf("%d %d", &u, &v); for(int i = 1; i <= t.n; i++) if(notRoot[i] == false) {rt = i; break;} t.tim = 0; t.dfs(rt, 1); //根节点深度为1 t.ST(2 * t.n - 1); int ans = t.LCA(u, v); printf("%d\n", ans); } return 0; }