树上倍增:维护 \(dp_{i,j}\) 表示节点 \(i\) 向上移动 \(2^j\) 步所到达的节点编号、区间最值、区间和等信息。
倍增求 LCA:
-
预处理:
-
令 \(dp_{i,j}\) 表示 \(i\) 向上走 \(2^j\) 步所到达的节点。
-
转移:\(dp_{i,j}=dp_{dp_{i,j-1},j-1}\)。
-
初始:\(dp_{i,0}=fa_i\)。
-
-
查询:
-
约定 \(x\) 深度更大。
-
\(x\) 倍增向上跳直到与 \(y\) 同深度。
-
特判 \(x,y\) 是否重叠。
-
\(x,y\) 一起向上跳,但不重叠(若重叠了还跳就不是 LCA 了)。
-
跳完后 \(fa_x\) 即为 LCA。
-
-
性质:
-
LCA 与根有关。
-
树上两点距离与 LCA 无关。
-
设 \(dis_x\) 表示树上节点 \(x\) 到根的距离,
\(LCA(x,y)\) 表示树上两点 \(x,y\) 的 LCA,
则树上两点 \(x,y\) 距离为 \(dis_x+dis_y-dis_{LCA(x,y)}\)。
-
P3379
板子。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=31;
int n,m,s;
vector<int> G[N<<1];
int dep[N],dp[N][M];
void initLCA(int cur,int fa){
dep[cur]=dep[fa]+1;
dp[cur][0]=fa;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i:G[cur])
if(i!=fa)
initLCA(i,cur);
}
int queryLCA(int x,int y){
if(dep[y]>dep[x]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[x][i]]>=dep[y])
x=dp[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>m>>s;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,
G[u].push_back(v),
G[v].push_back(u);
initLCA(s,0);
while(m--){
int x,y;
cin>>x>>y,cout<<queryLCA(x,y)<<'\n';
}
return 0;
}
P3398
因为树上节点不会有多个父节点,因此要么 \(LCA(a,b)\) 落在路径 \(c \to d\) 上,要么 \(LCA(c,d)\) 落在路径 \(a \to b\) 上。
检查一个点是否落在一条路径上,就检查该点到路径两端点的距离之和是否为路径长即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=31;
int n,q;
vector<int> G[N<<1];
int dep[N],dp[N][M];
void initLCA(int cur,int fa){
dep[cur]=dep[fa]+1;
dp[cur][0]=fa;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i:G[cur])
if(i!=fa)
initLCA(i,cur);
}
int queryLCA(int x,int y){
if(dep[y]>dep[x]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[x][i]]>=dep[y])
x=dp[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>q;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,
G[u].push_back(v),
G[v].push_back(u);
initLCA(1,0);
while(q--){
int a,b,c,d; cin>>a>>b>>c>>d;
int LCAab=queryLCA(a,b);
int LCAcd=queryLCA(c,d);
int LCAlc=queryLCA(LCAab,c);
int LCAld=queryLCA(LCAab,d);
int LCAla=queryLCA(LCAcd,a);
int LCAlb=queryLCA(LCAcd,b);
int DISab=dep[a]+dep[b]-2*dep[LCAab];
int DIScd=dep[c]+dep[d]-2*dep[LCAcd];
int DISlc=dep[LCAab]+dep[c]-2*dep[LCAlc];
int DISld=dep[LCAab]+dep[d]-2*dep[LCAld];
int DISla=dep[LCAcd]+dep[a]-2*dep[LCAla];
int DISlb=dep[LCAcd]+dep[b]-2*dep[LCAlb];
cout<<(DISlc+DISld==DIScd||DISla+DISlb==DISab?"Y\n":"N\n");
}
}
P4281
很容易发现一个性质:三个点两两的 LCA 必然只有两种可能(即必然会重合一个)。
简单画个图便可知,那个不一样的 LCA 一定是集合点。
继续看图分析,可得三个点 \(x,y,z\) 到集合点的距离即为:
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=31;
int n,m;
vector<int> G[N<<1];
int dep[N],dp[N][M];
void initLCA(int cur,int fa){
dep[cur]=dep[fa]+1;
dp[cur][0]=fa;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(int i:G[cur])
if(i!=fa)
initLCA(i,cur);
}
int queryLCA(int x,int y){
if(dep[y]>dep[x]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[x][i]]>=dep[y])
x=dp[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,
G[u].push_back(v),
G[v].push_back(u);
initLCA(1,0);
while(m--){
int x,y,z;
cin>>x>>y>>z;
int LCAxy=queryLCA(x,y);
int LCAxz=queryLCA(x,z);
int LCAyz=queryLCA(y,z);
int ans=0;
if(LCAxy==LCAxz) ans=LCAyz;
else if(LCAxy==LCAyz) ans=LCAxz;
else ans=LCAxy;
cout<<ans<<' '<<dep[x]+dep[y]+dep[z]-dep[LCAxy]-dep[LCAxz]-dep[LCAyz]<<'\n';
}
return 0;
}
CF379F
首先新直径一定得经过新加边(不会更劣),并且新加的两个点是等价的。
于是我们随便选个新加点 \(u\),令原直径端点为 \(x,y\),若 \(dis_{u,x}>dis_{x,y}\) 或 \(dis_{u,y}>dis_{x,y}\),则 \(u\) 可替代 \(x\) 或 \(y\)。
求距离时每次新加两个点就单独维护一下 LCA 即可。\(O(q \log n)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,M=31;
int m,n=4;
int dep[N<<1],dp[N<<1][M];
vector<int> G[N<<1];
void preLCA(int cur,int fa){
dp[cur][0]=fa;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
}
int queryLCA(int x,int y){
if(dep[y]>dep[x]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[x][i]]>=dep[y])
x=dp[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
int main(){
ios::sync_with_stdio(0);
dep[1]=1;
for(int i=2;i<=4;i++){
G[1].push_back(i);
dep[i]=2;
preLCA(i,1);
}
cin>>m;
int x=2,y=3,ans=2;
while(m--){
int u; cin>>u;
for(int i=1;i<=2;i++){
n++;
G[u].push_back(n);
G[n].push_back(u);
dep[n]=dep[u]+1;
preLCA(n,u);
}
int LCAnx=queryLCA(n,x);
int LCAny=queryLCA(n,y);
int DISnx=dep[n]+dep[x]-2*dep[LCAnx];
int DISny=dep[n]+dep[y]-2*dep[LCAny];
if(DISnx<=ans&&DISny<=ans)
cout<<ans<<'\n';
else if(DISnx>ans){
cout<<DISnx<<'\n';
ans=DISnx,y=n;
}
else{
cout<<DISny<<'\n';
ans=DISny,x=n;
}
}
return 0;
}
P8972
首先必须将边权都转为整数。
然后我们发现若干个小数相乘能为整数,则必须满足它们分解质因数后 \(2\) 或 \(5\) 中最少的个数必须 \(\ge\) 小数位数之和,这样才能抵消掉所有小数位数。
于是我们在 LCA 中维护 \(cnt2_x,cnt5_x,dis_x\) 分别表示节点 \(x\) 到根节点的 边权的质因子中 \(2\) 的个数、\(5\) 的个数,以及小数位数和,然后依上述条件判断即可。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5,M=31;
const int INF=1e16;
int n,q,a[N];
int cnt2[N],cnt5[N];
struct E{ int v,w,dot; };
vector<E> G[N<<1];
int dep[N],dp[N][M],dis[N];
int cntdiv(int x,int y){
if(!x) return INF;
int res=0;
while(x&&x%y==0)
x/=y,res++;
return res;
}
void initLCA(int cur,int fa){
dep[cur]=dep[fa]+1;
dp[cur][0]=fa;
for(int i=1;(1<<i)<=dep[cur];i++)
dp[cur][i]=dp[dp[cur][i-1]][i-1];
for(auto i:G[cur]){
if(i.v!=fa){
dis[i.v]=dis[cur]+i.dot;
cnt2[i.v]=cnt2[cur]+cntdiv(i.w,2);
cnt5[i.v]=cnt5[cur]+cntdiv(i.w,5);
initLCA(i.v,cur);
}
}
}
int queryLCA(int x,int y){
if(dep[y]>dep[x]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[dp[x][i]]>=dep[y])
x=dp[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(dp[x][i]!=dp[y][i])
x=dp[x][i],y=dp[y][i];
return dp[x][0];
}
signed main(){
ios::sync_with_stdio(0);
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int u,v; double w;
cin>>u>>v>>w;
int dot=0;
while(w!=floor(w))
w*=10.0,dot++;
G[u].push_back({v,floor(w),dot}),
G[v].push_back({u,floor(w),dot});
}
initLCA(1,0);
while(q--){
int x,y;
cin>>x>>y;
int LCAxy=queryLCA(x,y);
int DISxy=dis[x]+dis[y]-2*dis[LCAxy];
int two=cnt2[x]+cnt2[y]-2*cnt2[LCAxy]+cntdiv(a[x],2);
int five=cnt5[x]+cnt5[y]-2*cnt5[LCAxy]+cntdiv(a[x],5);
cout<<(min(two,five)>=DISxy?"Yes\n":"No\n");
}
return 0;
}