洛谷里面8页题解千篇一律,就没有用线段树求解的,这下不得不由本蒟蒻来生啃又臭又硬,代码又多的线段树了。
样例的欧拉序列:4 2 4 1 3 1 5 1 4
记录每个节点最早在欧拉序列中的时间,任意两个节点的LCA就是他们两个节点在欧拉序列之间,深度最小的那一个。(证明看洛谷题解)
比如2和5的LCA:4 2 4 1 3 1 5 1 4,4是深度最小的。
很显然这是RMQ问题,可以用ST表求解,但我偏不,多次询问区间求极值可以用线段树来维护。
此处维护的就是
注意建树内存大小是维护欧拉序列的大小,而不是原序列,欧拉序列是原序列的2倍大小,所以线段数要开8倍的内存。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+1;int n,m,s,oula[N<<1],d[N],ti[N],t[N<<3];//注意是8倍大小
vector<int>e[N];
bitset<N>vis;inline int read(){int x=0;char c=getchar();while(c<48 or c>57)c=getchar();while(c>=48 and c<=57)x=(x<<3)+(x<<1)+(c xor 48),c=getchar();return x;
}int main(){n=read(),m=read(),s=read();for(int i=1,x,y;i<=n-1;++i){x=read(),y=read();e[x].push_back(y);e[y].push_back(x);}d[s]=1,n=0;auto dfs=[](int x,auto dfs){if(vis[x])return;vis[x]=true;oula[++n]=x;//欧拉序列for(auto i:e[x]){if(!vis[i]){d[i]=d[x]+1;//深度dfs(i,dfs);oula[++n]=x;//欧拉序列}}};dfs(s,dfs);for(int i=1;i<=n;++i)if(!ti[oula[i]])ti[oula[i]]=i;//欧拉时间戳auto build=[](int l,int r,int i,auto build){if(l==r){t[i]=l;//叶子节点,欧拉时间戳return;}int mid=l+r>>1;build(l,mid,i<<1,build);build(mid+1,r,i<<1|1,build);t[i]=d[oula[t[i<<1]]]<d[oula[t[i<<1|1]]]?t[i<<1]:t[i<<1|1];};build(1,n,1,build);d[0]=INT_MAX;for(int i=1,a,b;i<=m;++i){//线段树维护欧拉时间戳a=read(),b=read();if(ti[a]>ti[b])swap(a,b);auto query=[](int l,int r,int i,int x,int y,auto query){int ans=0;if(l>=x and r<=y)return t[i];//返回欧拉时间戳int mid=l+r>>1;if(x<=mid){int idx=query(l,mid,i<<1,x,y,query);ans=d[oula[idx]]<d[oula[ans]]?idx:ans;}if(y>mid){int idx=query(mid+1,r,i<<1|1,x,y,query);ans=d[oula[idx]]<d[oula[ans]]?idx:ans;}return ans;};printf("%d\n",oula[query(1,n,1,ti[a],ti[b],query)]);}return 0;
}
需要注意的是,开O2并不会带来任何优化,本蒟蒻代码是厌氧代码。