LCA-tarjan understand 2
下面是一个最基础的LCA题目 http://poj.org/problem?id=1330
赤裸裸的 题意 输入cas 后 有cas组数据 输入 n 再输入n-1 条边 之后输入x y 问x y的最近公共祖先是什么
#include<stdio.h> #include<vector> #include<string.h> using namespace std; #define Size 11111 //节点个数 vector<int> node[Size],que[Size]; int n,pare[Size],anse[Size],in[Size],rank[Size]; int vis[Size]; void init() { int i; for(i=1;i<=n;i++) { node[i].clear(); que[i].clear(); rank[i]=1; pare[i]=i;/// } memset(vis,0,sizeof(vis)); memset(in,0,sizeof(in)); memset(anse,0,sizeof(anse)); } int find(int nd)//并查集操作 不解释 { return pare[nd]==nd?nd:pare[nd]=find(pare[nd]); } int Union(int nd1,int nd2)//并查集操作 不解释 { int a=find(nd1); int b=find(nd2); if(a==b) return 0; else if(rank[a]<=rank[b]) { pare[a]=b; rank[b]+=rank[a]; } else { pare[b]=a; rank[a]+=rank[b]; } return 1; } void LCA(int root) { int i,sz; anse[root]=root;//首先自成一个集合 sz=node[root].size(); for(i=0;i<sz;i++) { LCA(node[root][i]);//递归子树 Union(root,node[root][i]);//将子树和root并到一块 anse[find(node[root][i])]=root;//修改子树的祖先也指向root } vis[root]=1; sz=que[root].size(); for(i=0;i<sz;i++) { if(vis[que[root][i]]) { printf("%d\n",anse[find(que[root][i])]);///root和que[root][i]所表示的值的最近公共祖先 return ; } } return ; } int main() { int cas,i; scanf("%d",&cas); while(cas--) { int s,e; scanf("%d",&n); init(); for(i=0;i<n-1;i++) { scanf("%d %d",&s,&e); if(s!=e) { node[s].push_back(e); // node[e].push_back(s); in[e]++; } } scanf("%d %d",&s,&e); que[s].push_back(e); que[e].push_back(s); for(i=1;i<=n;i++) if(in[i]==0) break;//寻找根节点 // printf("root=%d\n",i); LCA(i); } return 0; }
之后来个加强版
http://acm.hdu.edu.cn/showproblem.php?pid=4547 CD操作 hdu4547
思路:
求出a和b的最近公共祖先,然后分4种情况讨论
①. a和b有一个公共祖先c,则用 c时间戳-a的时间戳+1(1步可以直接从c到b)
②. a是b的祖先,则只用1步就可以到达b点
③. b是a的祖先,则用a的时间戳-b的时间戳
④. a和b是同一个点,则答案是0
参考 http://www.cnblogs.com/Griselda/archive/2013/06/05/3119265.html
#include<stdio.h> #include<vector> #include<string.h> #include<map> #include<math.h> #include<string> using namespace std; #define Size 111111 //节点个数 struct Query { int nd,id; }temp; struct out { int s,e; }out[Size]; vector<int> node[Size]; vector<struct Query>que[Size]; int n,m,pare[Size],ance[Size],in[Size],rank[Size],dis[Size],ans[Size],vis[Size]; map<string,int>mp; void init() { int i; for(i=1;i<=n;i++) { node[i].clear(); que[i].clear(); rank[i]=1; pare[i]=i;/// } memset(vis,0,sizeof(vis)); memset(in,0,sizeof(in)); memset(ance,0,sizeof(ance)); memset(dis,0,sizeof(dis)); mp.clear(); } int aabs(int aa) { if(aa>0) return aa; else return -aa; } int find(int nd)//并查集操作 不解释 { return pare[nd]==nd?nd:pare[nd]=find(pare[nd]); } int Union(int nd1,int nd2)//并查集操作 不解释 { int a=find(nd1); int b=find(nd2); if(a==b) return 0; else if(rank[a]<=rank[b]) { pare[a]=b; rank[b]+=rank[a]; } else { pare[b]=a; rank[a]+=rank[b]; } return 1; } void LCA(int root,int num) { int i,sz; ance[root]=root;//首先自成一个集合 dis[root]=num; sz=node[root].size(); for(i=0;i<sz;i++) { LCA(node[root][i],num+1);//递归子树 Union(root,node[root][i]);//将子树和root并到一块 ance[find(node[root][i])]=root;//修改子树的祖先也指向root } vis[root]=1; sz=que[root].size(); for(i=0;i<sz;i++) { int nd1,nd2,idx,ancestor; nd1=root;nd2=que[root][i].nd;idx=que[root][i].id; if(vis[nd2]) { ans[idx]=ance[find(nd2)]; } } return ; } int main() { int cas,i; scanf("%d",&cas); while(cas--) { char ss[100],ee[100]; int s,e,cnt=1; scanf("%d %d",&n,&m); init(); for(i=0;i<n-1;i++) { scanf("%s %s",ee,ss); if(mp.find(ss)==mp.end()) { s=cnt;mp[ss]=cnt++; } else s=mp[ss]; if(mp.find(ee)==mp.end()) { e=cnt;mp[ee]=cnt++; } else e=mp[ee]; if(s!=e) { node[s].push_back(e); in[e]++; } } for(i=0;i<m;i++) { scanf("%s %s",ss,ee); s=mp[ss];e=mp[ee]; out[i].s=s;out[i].e=e; temp.nd=e;temp.id=i; que[s].push_back(temp); temp.nd=s;temp.id=i; que[e].push_back(temp); } for(i=1;i<=n;i++) if(in[i]==0) break;//寻找根节点 LCA(i,0); for(i=0;i<m;i++) { if(out[i].s==out[i].e) printf("0\n"); else if(out[i].s==ans[i]) printf("1\n"); else if(out[i].e==ans[i]) printf("%d\n",dis[out[i].s]-dis[ans[i]]); else printf("%d\n",dis[out[i].s]-dis[ans[i]]+1); } } return 0; }
hdu 2874
http://acm.hdu.edu.cn/showproblem.PHP?pid=2874
题目大意: 给你一个n个节点m条边的森林,再给定q个查询,每次查询森林里两个点的最近距离。n ,m <= 10000,q <= 100万
本题和标准的LCA模板应用有了不小的区别 却可以让人更加透彻的看清LCA的思路 而且本题没有必要去求出公共祖先
#include<stdio.h> #include<string.h> #include<vector> using namespace std; #define Size 11111 struct Edge { int y,val; }temp; struct Query { int y,id; }mid; int pare[Size],ance[Size],vis[Size],dis[Size],rank[Size],ans[1000000+100],n,m,c,tree[Size]; vector<struct Query>que[Size]; vector<struct Edge>node[Size]; void init() { int i; for(i=0;i<=n;i++) { vis[i]=0; pare[i]=i; dis[i]=0; rank[i]=1; que[i].clear(); node[i].clear(); } memset(ans,-1,sizeof(ans)); } int find(int x) { return pare[x]==x?x:pare[x]=find(pare[x]); } /* void Union(int x,int y) { x=find(x); y=find(y); if(x!=y) { if(rank[x]>rank[y]) { rank[x]+=rank[y]; pare[y]=x; } else { rank[y]+=rank[x]; pare[x]=y; } } } */ void LCA(int root,int d,int k)//k表示是以第k个点作为根的树 { int i,sz,nd1,nd2; vis[root]=1; //已经遍历过的点 要标记一下 不要 tree[root]=k;dis[root]=d; // ance[root]=root; sz=node[root].size(); for(i=0;i<sz;i++) { nd2=node[root][i].y; if(!vis[nd2]) { LCA(nd2,d+node[root][i].val,k); // Union(node[root][i].y,root);//用带rank的幷查集操作答案不对 不知道why int w=find(nd2),m=find(root); if(w!=m) { pare[w]=m;//这样才对 } //ance[find(node[root][i].y)]=root; } } sz=que[root].size(); for(i=0;i<sz;i++) { nd1=root; nd2=que[root][i].y; if(vis[nd2]&&tree[nd1]==tree[nd2])//如果 nd1 nd2 的跟是同一个点 则是同一棵树上的 { ans[que[root][i].id]=dis[nd1]+dis[nd2]-2*dis[find(nd2)]; } } } int main() { int i,j,x,y,val; while(scanf("%d %d %d",&n,&m,&c)!=EOF) { init(); for(i=0;i<m;i++) { scanf("%d %d %d",&x,&y,&val); if(x!=y) { temp.y=y;temp.val=val; node[x].push_back(temp); temp.y=x; node[y].push_back(temp);//路是2个方向都可以通行的 } } for(i=0;i<c;i++) { scanf("%d %d",&x,&y); mid.id=i; mid.y=y; que[x].push_back(mid); mid.y=x; que[y].push_back(mid); } for(i=1;i<=n;i++) { LCA(i,0,i);//以每一个节点作为根节点去深度搜索 找出每个点作为根的所有最近公共祖先 } for(i=0;i<c;i++) { if(ans[i]==-1) printf("Not connected\n"); else printf("%d\n",ans[i]); } } return 0; } /*本题给的是一个森林 而不是一颗树,由于在加入边的时候,我们让2个方向都能走 这样就 形成了一个强连通的快, 对于这个快来说,不管从快上那点出发 都可以遍历这个快上的所 有的点,且相对距离是一样的*/