http://blog.youkuaiyun.com/hnust_xiehonghao/article/details/9109295
离线算法其实就是dfs加并查集,在遍历图的时候进行查询,过程中不断地更新节点的父节点的值,确实是非常巧妙的,上面那个博客有很详细的介绍和说明
http://blog.youkuaiyun.com/jarjingx/article/details/8183240
在线倍增法,这个思想很容易应用到其他领域,我记得cf有一道题就是利用的这个思想,首先进行预处理,每个节点跳j步到达哪个节点,然后就是先把二者的深度调整为同样,然后利用二进制的性质从大到小,确定二者最后应该跳多少步,正好父节点就是最近公共祖先,这个方法非常值得学习,具体可以参看上面的博客
#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个方向都能走 这样就
形成了一个强连通的快, 对于这个快来说,不管从快上那点出发 都可以遍历这个快上的所
有的点,且相对距离是一样的*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define N 10010
#define M 20
int d[N],f[N][M];
vector<int>ch[N];
void dfs(int x){//求出所有结点深度
d[x]=d[f[x][0]]+1;
for(int i=1;i<M;i++)f[x][i]=f[f[x][i-1]][i-1];//倍增祖先
for(int i=ch[x].size()-1;i>=0;i--)
dfs(ch[x][i]);//遍历儿子
}
int lca(int x,int y){//求x,y最小公共祖先
if(d[x]<d[y])swap(x,y);//保证x的深度不小于y
int k=d[x]-d[y];
for(int i=0;i<M;i++)
if((1<<i)&k)
x=f[x][i];
if(x==y)return x;//x==y时为最小公共祖先
for(int i=M-1;i>=0;i--)
if(f[x][i]!=f[y][i]){
x=f[x][i];y=f[y][i];
}
return f[x][0];
}
int main(){
int T,n,i,x,y;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
memset(d,0,sizeof(d));
memset(f,0,sizeof(f));
memset(ch,0,sizeof(ch));
for(i=1;i<n;i++){
scanf("%d%d",&x,&y);
ch[x].push_back(y);
f[y][0]=x;
}
for(i=1;i<=n;i++)
if(f[i][0]==0){//找到根遍历
dfs(i);break;
}
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}