问题:
给定一棵 n 个点的树,Q 个询问,每次询问点 x 到点 y 两点之间的距离。
输入格式
第一行一个正整数 n(1<= n <=10^5),表示这棵树有 n 个节点;
接下来 n-1 行,每行两个整数 x, y (1<= x , y<=n )表示 x, y 之间有一条连边;
然后一个整数 Q,表示有 Q 个询问;
接下来 Q 行每行两个整数 x, y 表示询问 x 到 y 的距离。
输出格式
输出 Q 行,每行表示每个询问的答案。
样例
样例输入
6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6
样例输出
3
4
LCA求公共祖先:DFS+ST(RMQ)
两个关键理论:
任意一个数都可以用二进制去表示,拆分方法也很简单:例如20这个数,先找最接近但不大于20的一个2的幂次数,即16,然后20减去16等于4。再找最接近但不大于4的一个2的幂次数,即4。然后4-4=0,拆分结束,得到了16和4。如果c是a和b的LCA,那么c的所有祖先同样也是a和b的公共祖先,但不是最近的。
在ST算法中,我们维护了一个数组 dp[i][j],表示的是以下标为i为起点的长度为2^j的序列的信息。然后用动态规划的思想求出整个数组。刚才在上面说我们求LCA时一次要跳2的幂次方层,这就与dp数组中下标 j 的定义不谋而合了。所以我们定义倍增法中的dp[i][j]为:结点 i 的向上 2^j 层的祖先。
![](https://img-blog.csdnimg.cn/20191128192413545.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hYWDkwNA==,size_16,color_FFFFFF,t_70)
dp[4][1]=1;结点4的向上2^1=2层的祖先是结点1。
dp[10][1]=2;结点10的向上2^1=2层的祖先是结点2。
注意dp[6][0]=3,结点6的向上2^0=1层的祖先是3,即6的父节点。而这一现象正好可以当做DP的初始条件。dp[i][0]为i的父节点。下面写出递推式:dp[i][j] = dp[ dp[i][j-1] ] [j-1]。 如何理解这个递推式呢?dp[i][j-1]是结点i往上跳2^(j-1)层的祖先,那我们就在跳到这个结点的基础上,再向上跳2^(j-1)层,这样就相当于从结点i,先跳2^(j-1)层,再跳2^(j-1)层,最后还是到达了2^j层。
代码(模板):
#include<stdio.h> #include<string.h> #include<math.h> #define N 100005 #define M 25 #include<algorithm> using namespace std; int n,x,y,cot,m;//需要用到的变量 int dp[N][M],fa[N],book[N],deep[N];//算法用到的数组 int first[N],net[N*2],to[N*2];//邻接表数组 void init()//初始化 { cot=1; memset(deep,0,sizeof(deep)); memset(first,-1,sizeof(first)); memset(dp,0,sizeof(dp)); memset(book,0,sizeof(book)); } void build_bian(int x,int y)//构建邻接表 { to[cot]=y; net[cot]=first[x]; first[x]=cot++; } void dfs(int x,int father)//DFS找到边与边之间的关系 { book[x]=1; fa[x]=father;//主要就是要这个东西father表示x的父亲节点 for(int i=first[x]; i!=-1; i=net[i]) { int k=to[i]; if(k!=father)//防止回到父亲节点 dfs(k,x); } } int LCA(int x,int y) { if(deep[x]<deep[y])//确保x的深度大于y的深度,方便后面操作。 swap(x,y); for(int i=17; i>=0; i--)//若不能确保x的深度大于y,这一步中就无法确定往上跳的是x还是y if(deep[x]-(1<<i)>=deep[y]) x=dp[x][i]; if(x==y)//若二者处于同一深度后,正好相遇,则这个点就是LCA return x; for(int i=17; i>=0; i--)//x和y同时往上跳,从大到小遍历步长,遇到合适的就跳上去,不合适就减少步长 { if(dp[x][i]!=dp[y][i]) //若二者没相遇则跳上去 { x=dp[x][i]; y=dp[y][i]; } } return dp[x][0];//最后x和y跳到了LCA的下一层,LCA就是x和y的父节点 } int main() { init(); scanf("%d",&n); for(int i=1; i<n; i++) { scanf("%d%d",&x,&y); build_bian(x,y);//双向建边 build_bian(y,x); } dfs(1,0); for(int i=1; i<=n; i++)//处理deep数组和dp数组 { deep[i]=deep[fa[i]]+1; dp[i][0]=fa[i]; } for(int i=1; i<=n; i++) for(int j=1; j<=log2(n); j++) dp[i][j]=dp[dp[i][j-1]][j-1];//求出dp数组 scanf("%d",&m); while(m--) { scanf("%d%d",&x,&y); int L=LCA(x,y); int sum=(deep[x]+deep[y])-2*deep[L]; printf("%d\n",sum); } }