倍增法
在线算法,单独处理每个查询
DFS()------祖先递推 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
f a [ x ] [ i ] fa[x][i] fa[x][i]表示从 x x x向上跳 2 i 2^i 2i步能到达的祖先
f a [ x ] [ i ] = f a [ f a [ x ] [ i − 1 ] ] [ i − 1 ] fa[x][i]=fa[fa[x][i-1]][i-1] fa[x][i]=fa[fa[x][i−1]][i−1]
右端: f a [ x ] [ i − 1 ] fa[x][i-1] fa[x][i−1]=从 x x x向上跳 2 i − 1 2^{i-1} 2i−1步到达的点,从这个点再跳 2 i − 1 2^{i-1} 2i−1能到达 f a [ x ] [ i ] fa[x][i] fa[x][i]
利用二进制特征能跳到任意步的祖先
LCA()------同步上跳 O ( m l o g 2 n ) O(mlog_2n) O(mlog2n)
基本思路
- 把x\y提到相同深度
- 让x\y同步向上走,每走一步判断是否相遇
例1:零食采购
摘自题解:
列表项第i种零食是否包含 = 起始节点买到第i种零食总数 + 终点买到第i种零食总数 - 公共祖先买到第i种零食总数 - 公共祖先上一个节点买到第i种零食总数

cpp
const int N = 1e5+10,M=1050;
const double PI=acos(-1);
const long long mod =998244353, inf = 1e18 ;
int fa[N][25],dep[N];
int c[N][25];
// 分每种零食来统计 看这种零食能不能在路上买到 而不是直接统计种数
vector<int>g[N];
void dfs(int now,int f){
forr(i,1,20)c[now][i]+=c[f][i];
dep[now]=dep[f]+1;
fa[now][0]=f;
for(int i=1;(1<<i)<=dep[now];i++){
fa[now][i]=fa[fa[now][i-1]][i-1];
}
for(auto x:g[now]){
if(x==f)continue;
dfs(x,now);
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);//x深 y浅
//xy提到相同深度
reforr(i,0,20){
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
//从大到小遍历 利用二进制特性 分解dep[x]-dep[y]
}
if(x==y)return x;
//xy同步上跳
reforr(i,0,20){
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
//不是同一祖先就上跳 逐步逼近LCA
//fa[x][i]=fa[y][i]时不变 因为会有更近的相同祖先
}
return fa[x][0];
}
void solve(){
int n,q;cin>>n>>q;
forr(i,1,n){
int cx;cin>>cx;
c[i][cx]=1;
}
forr(i,1,n-1){
int u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);// 以1为根
forr(i,1,q){
int ans=0;
int s,t;cin>>s>>t;
int f=lca(s,t);
forr(k,1,20){// 统计每种零食是否存在
int cnt=c[s][k]+c[t][k]-c[f][k]-c[fa[f][0]][k];
ans+=(cnt>0);
}
cout<<ans<<endl;
}
}