LCA 返回最近公共祖先:预处理
三种祖先关系 a是b祖先 b是a祖先 a和b不是祖先关系
树上多个点的LCA,就是DFS序最小的和DFS序最大的这两个点的LCA
必备:知道根节点 必须存下来
有可能跳过根节点
int depth[N],f[][N];//N为(log节点数)+1
int q[N];
从根节点开始预处理;
需要设置0号点为哨兵
询问 p=lca(a,b)
向上标记法on:从一个点向根节点遍历标记公共祖先 ,然后另一个点也向上走 走到第一个标记过的位置 就是祖先
倍增法ologn 预处理nlogn 查询logn:f[i][j]从i开始 向上走2^j次方 走到的节点
f[i][0]i点向上走一步走到的节点 f[i][1]向上走两步走到的节点 f[i][2]向上走四步发现不存在值为空集
当j>0 f[i][j]=f[i][j-1]基础上2^j-1步
depth表示深度从上往下看的 根节点深度是1 i表示i节点的深度
于是lca
哨兵:如果f[i][j]跳过根节点那么设为0 0节点的深度为0
第一步让两个点跳到同一层: t=相差depth(x)-depth(y)层 根据t的二进制跳 如果跳了不在y的上面那就跳:depth(f(x,k))>depth(y) 所以挑了之后就还能跳
第二步让两个点同时往上跳,直到跳到最近公共祖先的下一层:此时最近公共祖先为f[i][0]
祖孙查询https://www.acwing.com/activity/content/problem/content/1537/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 40010,M=2*N;
int n,m;
int h[N], e[M], ne[M], idx;
int depth[N],fa[N][16];
int q[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs(int root){//从上到下
memset(depth,0x3f , sizeof depth);
depth[0]=0,depth[root]=1;//0号哨兵点深度为0,根节点深度为1
int hh=0,tt=0;//bfs队列
q[0]=root;
while(hh<=tt){
int t=q[hh++];
for (int i = h[t]; ~i ; i =ne[i] ){
int j=e[i];//对于当前点的子节点
if(depth[j]>depth[t]+1){//既然连了边,又比+1大说明是前面设置0正无穷的缘故
depth[j]=depth[t]+1;
q[++tt]=j;
fa[j][0]=t;
for (int k = 1; k <= 15; k ++ ){//因为fa初始化是0所以只要走不上求自动为0
fa[j][k]=fa[fa[j][k-1]][k-1];//j点在上面第k-1的祖先往上走k-1步
}
}
}
}
}
int lca(int a,int b){//两个fork从大到小实现
if(depth[a]<depth[b]) swap(a,b);//要求a在b的下面 不满足就换
for (int k = 15; k >=0; k -- ){//从大到小开始走
if(depth[fa[a][k]]>=depth[b])
a=fa[a][k];
}
if(a==b) return a;
for (int k = 15; k >=0; k -- ){
if(fa[a][k]!=fa[b][k]){//说明不在公共祖先上 一起跳
a=fa[a][k];
b=fa[b][k];
}
}
return fa[a][0];//向上走一步
}
int main()
{
cin >> n;
int root=0;
memset(h, -1, sizeof h);
for (int i = 0; i < n; i ++ ){
int a,b;
cin >> a>>b;
if(b==-1) root=a;
else add(a,b),add(b,a);
}
bfs(root);//预处理
cin >> m;
while (m -- ){
int a,b;cin>>a>>b;
int p=lca(a,b);
if(p==a) cout << 1<<endl;
else if(p==b) cout << 2<<endl;
else cout << 0<<endl;
}
return 0;
}
tarjan 离线求LCA
一递归搜索分成三类+并查集查询
1.已经遍历到的点且回溯的点 2.正在搜素的分支 3.还未搜索到的点
求公共祖先就是看下在1的a点的祖宗节点是谁
距离https://www.acwing.com/problem/content/1173/
xy的距离是 d[x]+[y]-2*d[p];
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define pii pair<int,int>
const int N = 20010,M=2*N;
int n,m;
int h[N], e[M], ne[M], idx,w[M];
int p[N];
int dist[N];
vector<pii>query[N];
// query[i][first][second] first存查询距离i的另外一个点j,second存查询编号idx
int st[N];
int res[N];
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u,int fa){//搜索设置所有距离
for (int i =h[u];~i ; i=ne[i] ){
int j=e[i];
if(j==fa) continue;//不能网上搜
dist[j]=dist[u]+w[i];
dfs(j,u);
}
}
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void tarjan(int u){
st[u]=1;//表示正在搜
for (int i = h[u]; ~i ; i =ne[i] ){
int j=e[i];// u这条路上的根节点的左下的点用并查集合并到根节点
if(!st[j]){
tarjan(j);//往左边搜 搜完在合并
p[j]=u;//从左下会输后把左边的点合并到根节点
}
}
//对于当前点u 搜索所有和u相关的查询 这步能及时处理查询 不然合并了就查不出来了
for ( auto item:query[u] ){//遍历和u相关的查询
int y=item.first,id=item.second;
if(st[y]==2){//如果这个点已经是之前搜索过的 那么就可以找了
int anc=find(y);//直接找到两者的公共祖先
res[id]=dist[u]+dist[y]-2*dist[anc];
}
}
st[u]=2;//表示已经搜过
}
int main()
{
// int n,m;
cin >>n>>m;
memset(h, -1, sizeof h);
for (int i = 0; i < n-1; i ++ ){
int a,b,c;
cin >> a>>b>>c;
add(a, b, c);
add(b, a, c);
}
for (int i = 0; i < m; i ++ ){
int a,b;
cin >> a>>b;
if(a!=b){
query[a].push_back({b,i});//数组存入关于a节点的询问,另一个节点和第几个查询
query[b].push_back({a,i});
}
}
for (int i = 1; i <= n; i ++ ) p[i]=i;//并查集
dfs(1,-1);
tarjan(1);
for (int i = 0; i < m; i ++ ){
cout << res[i]<<endl;
}
return 0;
}
求对AB树上集合的某一个点删除后求lca a的值比b的值大的方案
因为集合的点确定而且排列组合麻烦所以枚举点
树上多个点的LCA,就是DFS序最小的和DFS序最大的这两个点的LCA
//deep数组记录的是树上每个节点的深度,fa数组记录的是每个点的父节点,这两个是倍增lca需要的
int deepA[N], faA[N][31], deepB[N], faB[N][31];
//st数组记录每个点的dfs序,w数组记录每个点的权值
int stA[N], stB[N], wA[N], wB[N];
//树数组
vector<int>treeA[N], treeB[N];
bool cmpA(int a, int b)
{
return stA[a] < stA[b];
}
bool cmpB(int a, int b)
{
return stB[a] < stB[b];
}
// dfs,用来为 lca 算法做准备。接受两个参数:当前结点,它的父亲节点,cnt的数注意传入指针修改
void dfsA(int root, int fno, int& cnt) {
// 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
faA[root][0] = fno;
stA[root] = cnt++;//记录dfs序!!
deepA[root] = deepA[faA[root][0]] + 1;
// 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
// 2^(i-1) 的祖先节点。
for (int i = 1; i < 31; ++i) {
faA[root][i] = faA[faA[root][i - 1]][i - 1];
}
// 遍历子节点来进行 dfs。
int sz = treeA[root].size();
for (int i = 0; i < sz; ++i) {
if (treeA[root][i] == fno) continue;
dfsA(treeA[root][i], root, cnt);
}
}
void dfsB(int root, int fno, int& cnt) {
faB[root][0] = fno;
stB[root] = cnt++;
deepB[root] = deepB[faB[root][0]] + 1;
for (int i = 1; i < 31; ++i) {
faB[root][i] = faB[faB[root][i - 1]][i - 1];
}
int sz = treeB[root].size();
for (int i = 0; i < sz; ++i) {
if (treeB[root][i] == fno) continue;
dfsB(treeB[root][i], root, cnt);
}
}
// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lcaA(int x, int y) {
// 令 y 比 x 深。
if (deepA[x] > deepA[y]) swap(x, y);
// 令 y 和 x 在一个深度。
int tmp = deepA[y] - deepA[x], ans = 0;
for (int j = 0; tmp; ++j, tmp >>= 1)
if (tmp & 1) y = faA[y][j];
// 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
if (y == x) return x;
// 不然的话,找到第一个不是它们祖先的两个点。
for (int j = 30; j >= 0 && y != x; --j) {
if (faA[x][j] != faA[y][j]) {
x = faA[x][j];
y = faA[y][j];
}
}
// 返回结果。
return faA[x][0];
}
int lcaB(int x, int y) {
if (deepB[x] > deepB[y]) swap(x, y);
int tmp = deepB[y] - deepB[x], ans = 0;
for (int j = 0; tmp; ++j, tmp >>= 1)
if (tmp & 1) y = faB[y][j];
if (y == x) return x;
for (int j = 30; j >= 0 && y != x; --j) {
if (faB[x][j] != faB[y][j]) {
x = faB[x][j];
y = faB[y][j];
}
}
return faB[x][0];
}
void solve()
{
int n, m, x;
cin >> n >> m;
vector<int>ansA(m), ansB(m);
for (int i = 0; i < m; i++)cin >> ansA[i], ansB[i] = ansA[i];
for (int i = 1; i <= n; i++)cin >> wA[i];
for (int i = 2; i <= n; i++)
{
cin >> x;
treeA[x].push_back(i);
}
int cnt = 1;
//预处理lca,并且求得每个点的dfs序
dfsA(1, 0, cnt);
for (int i = 1; i <= n; i++)cin >> wB[i];
for (int i = 2; i <= n; i++)
{
cin >> x;
treeB[x].push_back(i);
}
cnt = 1;
dfsB(1, 0, cnt);
//按照dfs序对k集合的点进行升序排序
sort(ansA.begin(), ansA.end(), cmpA);
sort(ansB.begin(), ansB.end(), cmpB);
cnt = 0;
//我们以A树的k集合为标准枚举所有的点
for (int i = 0; i < m; i++)
{
//x记录k集合中dfs序最小的点,y记录最大的点
int xA = ansA[0], yA = ansA[m - 1], xB = ansB[0], yB = ansB[m - 1];
//如果最大的点被删除,我们取第二大的
if (i == 0)xA = ansA[1];
//如果最小的点被删除,我们取第二小的
else if (i == m - 1)yA = ansA[m - 2];
if (xB == ansA[i])xB = ansB[1];
else if (yB == ansA[i])yB = ansB[m - 2];
//求的两个树的最近公共祖先
int xlca = lcaA(xA, yA), ylca = lcaB(xB, yB);
if (wA[xlca] > wB[ylca])cnt++;
}
cout << cnt << endl;
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}