1.朴素法
d数组记录的是u,v节点到根节点的深度
int lca(int u,int v){
if(d[u]<d[v]){ //保证u所在的节点比较深
swap(u,v);
}
while(d[u]!=d[v]){ //让u和v处于同一深度
u=father[u];
}
while(u!=v){ //将u和v同时上调
u=father[u];
v=father[v];
}
return u;
}
2.离线Tarjan算法 链接
一次性将要计算最近共同祖先的u,v读入,然后dfs的时候处理这些u和v
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int pre[maxn];
const int inf=1e9+7;
struct edge{
int from,to,val;
};
bool compare(edge a,edge b){
return a.val>b.val;
}
int find(int x){
if(x!=pre[x]){
pre[x]=find(pre[x]);
}
return pre[x];
}
void unite(int x,int y){
pre[find(x)]=find(y);
}
map<pair<int,int>,int>mp; //保存的边之间的权值
map<pair<int,int>,int>ans; //询问的答案
vector<int>vec[maxn];
vector<int>q[maxn];
vector<edge>shu;
pair<int,int>p[maxn];
int flag[maxn];
edge a[maxn];
int n,m,query;
int dis[maxn];
void dfs(int u,int fa){
int minn=1e9+7;
for(int i=0;i<vec[u].size();i++){
if(vec[u][i]!=fa){
minn=min(minn,mp[{u,vec[u][i]}]);
dfs(vec[u][i],u);
}
}
dis[u]=minn;
}
void tarjan(int u,int fa){
for(int i=0;i<vec[u].size();i++){
if(vec[u][i]!=fa){
tarjan(vec[u][i],u);
pre[find(vec[u][i])]=find(u);
}
}
flag[u]=1;
for(int i=0;i<q[u].size();i++){
int now=q[u][i];
if(flag[now]){
cout<<u<<"和"<<now<<"的最近共同祖先是"<<find(now)<<"\n";
ans[{u,now}]=dis[find(now)];
ans[{now,u}]=dis[find(now)];
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
pre[i]=i;
}
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
a[i].from=u,a[i].to=v,a[i].val=w;
}
sort(a+1,a+m,compare);
int start=0;
for(int i=1;i<=m;i++){
int from=a[i].from,to=a[i].to,val=a[i].val;
if(find(from)==find(to)){
continue;
}
else{
unite(from,to);
shu.push_back(a[i]);
}
}
for(int i=shu.size()-1;i>=0;i--){ //从最大生成树的最短边为树根开始构建树
edge now=shu[i];
int from=now.from,to=now.to,val=now.val;
//cout<<"from "<<from<<" to "<<to<<" val "<<val<<"\n";
if(!start) start=from;
vec[from].push_back(to);
vec[to].push_back(from);
mp[{from,to}]=val;
mp[{to,from}]=val;
}
int cnt=0;
cin>>query;
while(query--){
int u,v;
cin>>u>>v;
p[++cnt].first=u,p[cnt].second=v;
if(find(u)!=find(v)){
ans[{u,v}]=ans[{v,u}]=-1;
continue;
}
q[u].push_back(v);
q[v].push_back(u);
}
memset(dis,-inf,sizeof(dis));
for(int i=1;i<=n;i++){
pre[i]=i;
}
dfs(start,-1);
tarjan(start,-1);
for(int i=1;i<=cnt;i++){
cout<<p[i].first<<" "<<p[i].second<<"\n";
cout<<ans[p[i]]<<"\n";
}
}
3.倍增求lca 链接
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int pre[maxn][30],depth[maxn];
vector<int>vec[maxn];
int from[maxn],root=-1;
int n;
void dfs1(int u){
for(int i=0;i<vec[u].size();i++){
int now=vec[u][i];
depth[now]=depth[u]+1;
dfs1(now);
}
}
void dfs2(int u){
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i<=n;i++){
pre[i][j]=pre[pre[i][j-1]][j-1];
}
}
}
int lca(int u,int v){
if(depth[u]<depth[v]) swap(u,v); //保证u的深度>=v的深度
for(int i=20;i>=0;i--){
if(depth[pre[u][i]]>=depth[v]){
u=pre[u][i];
}
if(u==v){ //到达相同深度时两个节点相遇
return u;
}
}
for(int i=20;i>=0;i--){ //到达相同深度时两个节点没有相遇
if(pre[u][i]!=pre[v][i]){
u=pre[u][i];
v=pre[v][i];
}
}
return pre[u][0];
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
vec[u].push_back(v);
pre[v][0]=u;
from[v]=1;
}
for(int i=1;i<=n;i++){
if(from[i]==0){
root=i;
}
}
depth[root]=1;
dfs1(root);
dfs2(root);
int q;
cin>>q;
while(q--){
int u,v;
cin>>u>>v;
cout<<u<<"和"<<v<<"的最近共同祖先是"<<lca(u,v)<<"\n";
}
}
/*
9
1 2
1 3
1 4
2 5
2 6
3 7
6 8
7 9
5
1 3
5 6
8 9
8 4
5 8
*/
4.树上差分
1.边差分
times[i]作为差分数组,然后dfs一遍从下面往上记录子树的答案,求从子树到根的后缀和,最后算到times[u]的时候,times[u]=times[u]+所有子树的times[v],此时times[u]代表u->fa[u]这条边的权值大小
#include<bits/stdc++.h>
using namespace std;
int n;
const int maxn=2e5+5;
int depth[maxn],pre[maxn][40];
vector<int>vec[maxn];
int times[maxn]; //times[i]代表以i为儿子的边出现的次数
vector<pair<int,int>>ans;
map<pair<int,int>,int>mp;
void dfs1(int u,int fa){
depth[u]=depth[fa]+1;
pre[u][0]=fa;
for(int i=0;i<vec[u].size();i++){
int now=vec[u][i];
if(now!=fa){
dfs1(now,u);
}
}
}
void dfs2(){
for(int j=1;j<=30;j++){
for(int i=1;i<=n;i++){
pre[i][j]=pre[pre[i][j-1]][j-1];
}
}
}
void dfs3(int u,int fa){ //统计出现次数的时候是把儿子的值往上面推
for(int i=0;i<vec[u].size();i++){
int now=vec[u][i];
if(now==fa){
continue;
}
else{
dfs3(now,u);
times[u]+=times[now];
}
}
mp[{fa,u}]=times[u];
mp[{u,fa}]=times[u];
}
int lca(int u,int v){
if(depth[u]<depth[v]){
swap(u,v);
}
for(int i=30;i>=0;i--){
if(depth[pre[u][i]]>=depth[v]){
u=pre[u][i];
if(u==v){
return v;
}
}
}
for(int i=30;i>=0;i--){
if(pre[u][i]!=pre[v][i]){
u=pre[u][i];
v=pre[v][i];
}
}
return pre[u][0];
}
int main(){
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
ans.push_back(make_pair(u,v));
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs1(1,0);
dfs2();
int m;
cin>>m;
while(m--){
int u,v;
cin>>u>>v;
int zuxian=lca(u,v);
times[u]+=1;
times[v]+=1;
times[zuxian]-=2;
}
dfs3(1,0);
//cout<<ans.size()<<"\n";
for(auto it:ans){
cout<<mp[it]<<" ";
}
}
2.点差分
与边差分不同的是 算到last[u]的时候last[u]+所有儿子last[v]的值now代表u这个节点的值的大小,而边差分的now代表的是u到它的父亲那条边的值的大小
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
vector<int>vec[maxn];
int depth[maxn];
int pre[maxn][31];
int last[maxn];
int maxx=0;
int n,m;
void dfs1(int u,int fa){
depth[u]=depth[fa]+1;
pre[u][0]=fa;
for(int v:vec[u]){
if(v!=fa){
dfs1(v,u);
}
}
}
void dfs2(int u,int fa){
for(int v:vec[u]){
if(v!=fa){
dfs2(v,u);
last[u]+=last[v];
}
}
}
void init(){
for(int j=1;j<=30;j++){
for(int i=1;i<=n;i++){
pre[i][j]=pre[pre[i][j-1]][j-1];
}
}
}
int lca(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
for(int i=30;i>=0;i--){
if(depth[pre[u][i]]>=depth[v]){
u=pre[u][i];
}
if(u==v){
return u;
}
}
for(int i=30;i>=0;i--){
if(pre[u][i]!=pre[v][i]){
u=pre[u][i];
v=pre[v][i];
}
}
return pre[u][0];
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs1(1,0);
init();
while(m--){
int u,v;
cin>>u>>v;
last[u]+=1;
last[v]+=1;
int fa=lca(u,v);
//cout<<"lca "<<fa<<"\n";
last[fa]-=1;
last[pre[fa][0]]-=1;
}
dfs2(1,0);
//cout<<last[4]<<"\n";
for(int i=1;i<=n;i++){
maxx=max(maxx,last[i]);
}
cout<<maxx<<"\n";
}