两个点的最近公共祖先,即两个点的所有公共祖先中,离根节点最远的一个节点。
【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N , M , S N,M,S N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N − 1 N-1 N−1 行每行包含两个正整数 x , y x, y x,y,表示 x x x 结点和 y y y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M M M 行每行包含两个正整数 a , b a, b a,b,表示询问 a a a 结点和 b b b 结点的最近公共祖先。
输出格式
输出包含 M M M 行,每行包含一个正整数,依次为每一个询问的结果。
样例 #1
样例输入 #1
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
样例输出 #1
4
4
1
4
4
提示
对于 30 % 30\% 30% 的数据, N ≤ 10 N\leq 10 N≤10, M ≤ 10 M\leq 10 M≤10。
对于 70 % 70\% 70% 的数据, N ≤ 10000 N\leq 10000 N≤10000, M ≤ 10000 M\leq 10000 M≤10000。
对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 500000 1 \leq N,M\leq 500000 1≤N,M≤500000, 1 ≤ x , y , a , b ≤ N 1 \leq x, y,a ,b \leq N 1≤x,y,a,b≤N,不保证 a ≠ b a \neq b a=b。
样例说明:
该树结构如下:
第一次询问: 2 , 4 2, 4 2,4 的最近公共祖先,故为 4 4 4。
第二次询问: 3 , 2 3, 2 3,2 的最近公共祖先,故为 4 4 4。
第三次询问: 3 , 5 3, 5 3,5 的最近公共祖先,故为 1 1 1。
第四次询问: 1 , 2 1, 2 1,2 的最近公共祖先,故为 4 4 4。
第五次询问: 4 , 5 4, 5 4,5 的最近公共祖先,故为 4 4 4。
故输出依次为 4 , 4 , 1 , 4 , 4 4, 4, 1, 4, 4 4,4,1,4,4。
2021/10/4 数据更新 @fstqwq:应要求加了两组数据卡掉了暴力跳。
解题思路
模板题,可以使用倍增,tarjan,树链剖分三种方法来解决,具体模板可以参照
最近公共祖先(倍增,tarjan,树链剖分)
斐波那契
题目描述
小 C 养了一些很可爱的兔子。 有一天,小 C 突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行 繁衍:一对兔子从出生后第二个月起,每个月刚开始的时候都会产下一对小兔子。我们假定, 在整个过程中兔子不会出现任何意外。
小 C 把兔子按出生顺序,把兔子们从 1 开始标号,并且小 C 的兔子都是 1 号兔子和 1 号兔子的后代。如果某两对兔子是同时出生的,那么小 C 会将父母标号更小的一对优先标 号。
如果我们把这种关系用图画下来,前六个月大概就是这样的:
其中,一个箭头 A → B A \to B A→B 表示 A A A 是 B B B 的祖先,相同的颜色表示同一个月出生的兔子。
为了更细致地了解兔子们是如何繁衍的,小 C 找来了一些兔子,并且向你提出了 m m m 个问题:她想知道关于每两对兔子 a i a_i ai 和 b i b_i bi,他们的最近公共祖先是谁。你能帮帮小 C 吗?
一对兔子的祖先是这对兔子以及他们父母(如果有的话)的祖先,而最近公共祖先是指 两对兔子所共有的祖先中,离他们的距离之和最近的一对兔子。比如, 5 5 5 和 7 7 7 的最近公共祖 先是 2 , 1 2,1 2,1 和 2 2 2 的最近公共祖先是 1 , 6 1,6 1,6 和 6 6 6 的最近公共祖先是 6 6 6。
输入格式
输入第一行,包含一个正整数 m m m。输入接下来 m m m 行,每行包含 2 2 2 个正整数,表示 a i a_i ai 和 b i b_i bi。
输出格式
输出一共 m m m 行,每行一个正整数,依次表示你对问题的答案。
样例 #1
样例输入 #1
5
1 1
2 3
5 7
7 13
4 12
样例输出 #1
1
1
2
2
4
提示
【数据范围与约定】 子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解决一部分测试数据。 每个测试点的数据规模及特点如下表:
特殊性质 1 1 1:保证 a i a_i ai, b i b_i bi 均为某一个月出生的兔子中标号最大的一对兔子。例如,对 于前六个月,标号最大的兔子分别是 1 , 2 , 3 , 5 , 8 , 13 1, 2, 3, 5, 8, 13 1,2,3,5,8,13。
特殊性质 2 2 2:保证 ∣ a i − b i ∣ ≤ 1 |a_i-b_i|\le 1 ∣ai−bi∣≤1。
解题思路
规律题,可以发现父与子之间编号的差为小于子结点编号的最大斐波那契数,例如2的子节点有5,7,10,差值分别为3,5,8,均为斐波那契数,因此寻找最小公共祖先向上寻路时每次要到的点的编号都是当前点的编号减去小于他的最大斐波那契数。
code
#include<iostream>
using namespace std;
long long d[70];
int main()
{
int m;
d[1]=1;d[2]=1;
for(int i=3;i<=60;i++)d[i]=d[i-1]+d[i-2];
cin>>m;
long long a,b;
for(int i=1;i<=m;i++)
{
scanf("%lld %lld",&a,&b);
while(a!=b)
{
if(a<b)swap(a,b);
for(int i=59;i>=1;i--)
if(a-d[i]>0)
{
a-=d[i];
break;
}
}
printf("%lld\n",a);
}
return 0;
}
[AHOI2008] 紧急集合 / 聚会
题目描述
欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有 n n n 个等待点,有 n − 1 n-1 n−1 条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。
参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在 n n n 个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。
小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?
输入格式
第一行两个正整数 n n n 和 m m m,分别表示等待点的个数(等待点也从 1 1 1 到 n n n 进行编号)和获奖所需要完成集合的次数。
随后 n − 1 n-1 n−1 行,每行两个正整数 a , b a,b a,b,表示编号为 a a a 和编号为 b b b 的等待点之间有一条路。
随后 m m m 行,每行用三个正整数 x , y , z x,y,z x,y,z,表示某次集合前小可可、小可可的朋友以及你所在等待点的编号。
输出格式
输出共 m m m 行,每行两个用空格隔开的整数 p , c p,c p,c。其中第 i i i 行表示第 i i i 次集合点选择在编号为 p p p 的等待点,集合总共的花费是 c c c 个游戏币。
样例 #1
样例输入 #1
6 4
1 2
2 3
2 4
4 5
5 6
4 5 6
6 3 1
2 4 4
6 6 6
样例输出 #1
5 2
2 5
4 1
6 0
提示
对于 40 % 40\% 40% 的数据, n ≤ 2 × 1 0 3 n\leq2\times10^3 n≤2×103, m ≤ 2 × 1 0 3 m\leq2\times 10^3 m≤2×103。
对于 100 % 100\% 100% 的数据, 1 ≤ x , y , z ≤ n ≤ 5 × 1 0 5 1\leq x,y,z\leq n\leq 5\times10^5 1≤x,y,z≤n≤5×105, 1 ≤ m ≤ 5 × 1 0 5 1\leq m\leq 5\times 10^5 1≤m≤5×105。
解题思路
题目要求得出三个点走到同一个点所要求的最小花费,经过我们的分析可以得出这个公共的点较其他点优的情况可以通过 求三个点两两之间的LCA得出,然后我们发现这三个LCA中最少有二者重合即它最多存在两种情况:最后三者所走到的最优公共点只可能为这二者之一。然后经过分析可以得到不重合的公共点才是最优解。
code
#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 500000
int n,m;
vector<int>e[MAX_N+5];
int dep[MAX_N*2+5];
int fa[MAX_N+5][20];
void dfs(int now,int father)
{
dep[now]=dep[father]+1;
fa[now][0]=father;
for(int i=1;i<=19;i++)
fa[now][i]=fa[fa[now][i-1]][i-1];
for(auto v:e[now])
{
if(v==father)continue;
dfs(v,now);
}
return ;
}
int lca(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(u==v)return u;
for(int i=19;i>=0;i--)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int main()
{
cin>>n>>m;
for(int i=1,a,b;i<=n-1;i++)
{
scanf("%d%d",&a,&b);
e[a].push_back(b);
e[b].push_back(a);
}
dfs(1,0);
for(int i=1,a,b,c;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
int t1=lca(a,b);
int t2=lca(a,c);
int t3=lca(b,c);
int t;
if(t1==t2)t=t3;
else if(t1==t3)t=t2;
else t=t1;
int ans=dep[a]+dep[b]+dep[c]-dep[t1]-dep[t2]-dep[t3];
printf("%d %d\n",t,ans);
}
return 0;
}