tarjan做法:
离线
总时间复杂度是O(nlogn+q)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=500010;
struct ty{
ll t,next;
}edge[N<<1];
ll cnt1=0;
ll head[N];
ll n,m,s;
ll a,b;
template<typename Type>inline void read(Type &xx)
{
Type f=1;char ch;xx=0;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
xx*=f;
}
struct ty2//询问结构体
{
ll t,next;
ll same;//会有一组与其相同的询问,
//每次遍历到一组时要同时把另一组去掉
ll num;//询问的编号,最后要按顺序输出
bool flag=0;//标记该询问是否已经被回答过
}q[N<<1];
void addedge(ll a,ll b)
{
edge[++cnt1].t=b;
edge[cnt1].next=head[a];
head[a]=cnt1;
}
ll cnt2=0;
ll head2[N];
ll fa[N];//并查集
void addque(ll a,ll b,ll c/*记录序号*/)
{
q[++cnt2].t=b;
q[cnt2].next=head2[a];
head2[a]=cnt2;
q[cnt2].same=cnt2+1;
q[cnt2].num=c;
q[++cnt2].t=a;
q[cnt2].next=head2[b];
head2[b]=cnt2;
q[cnt2].num=c;
q[cnt2].same=cnt2-1;
}
ll find(ll x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void comb(ll x,ll y)
{
fa[find(x)]=find(y);//此处只要找到其父亲,不需要找到其祖先
}
ll vis[N];//标记该节点是否被访问过
ll ans[N];//记录答案
void lca(ll p,ll fa)
{
vis[p]=1;
for(int i=head[p];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(y==fa) continue;
if(vis[y]) continue;
lca(y,p);
comb(y,p);
}
for(int i=head2[p];i!=-1;i=q[i].next)
{
ll y=q[i].t;
if(!vis[y]) continue;//这必须是一个被访问过的节点,不然没有信息可以做
if(q[i].flag) continue;//代表这个询问已经做过了
ans[q[i].num]=find(y);//记录答案
q[i].flag=1;
q[q[i].same].flag=1;
}
}
int main()
{//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
read(n);read(m);read(s);
memset(head,-1,sizeof head);
memset(head2,-1,sizeof head2);
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=n-1;++i)
{
read(a);read(b);
addedge(a,b);
addedge(b,a);
} //建树;
for(int i=1;i<=m;++i)//离线建询问
{
read(a);read(b);
addque(a,b,i);
}
lca(s,-1);
for(int i=1;i<=m;++i)
{
printf("%lld\n",ans[i]);
}
return 0;
}
倍增:
将所查的两个点先放到同一深度,再一起向上走直到相见
但是维护每一个节点上面的每一级的祖先是做不到的,空间扛不住,所以采用倍增思路。
时间复杂度为O(nlogn)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=500010;
struct ty{
ll t,next;
}edge[N<<1];
ll cnt1=0;
ll head[N];
ll n,m,s;
ll a,b;
template<typename Type>inline void read(Type &xx)
{
Type f=1;char ch;xx=0;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
xx*=f;
}
void add(ll a,ll b)
{
edge[++cnt1].t=b;
edge[cnt1].next=head[a];
head[a]=cnt1;
}
ll lg[N];
ll fa[N][22];
ll depth[N];
void dfs(ll u,ll p)
{
fa[u][0]=p;
depth[u]=depth[p]+1;
for(int i=1;i<+lg[depth[u]];++i)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(y==p) continue;
dfs(y,u);
}
}
ll lca(ll x,ll y)
{
if(depth[x]<depth[y]) swap(x,y);
while(depth[x]>depth[y])
{
x=fa[x][lg[depth[x]-depth[y]]-1];
}
if(x==y) return x;
for(int i=lg[depth[x]]-1;i>=0;--i)
{
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int main()
{//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
read(n);read(m);read(s);
memset(head,-1,sizeof head);
for(int i=1;i<=n-1;++i)
{
read(a);read(b);
add(a,b);
add(b,a);
} //建树;
for(int i=1;i<=n;++i)
{
lg[i]=lg[i-1]+((1<<lg[i-1])==i);
}
dfs(s,0);
for(int i=1;i<=m;++i)
{
read(a);read(b);
printf("%lld\n",lca(a,b));
}
return 0;
}
RMQ:
ST表+dfs序,将树掰成线性表来处理
还是要注意ST表的边界处理
时间复杂度O(n*log2(n)),查询O(1)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+10;
ll first[N];//每一个字符第一次出现的时候
template<typename Type>inline void read(Type &xx)
{
Type f=1;char ch;xx=0;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())xx=xx*10+ch-'0';
xx*=f;
}
struct ty{
ll t,next;
}edge[N];
ll cnt=0;
ll head[N];
void add(ll a,ll b)
{
edge[++cnt].t=b;
edge[cnt].next=head[a];
head[a]=cnt;
}
ll ver[N];//dfs第几个访问的节点
ll r[N]; //节点的深度
ll tot=0;//访问的序号
void dfs(ll u,ll dep)
{
first[u]=++tot;ver[tot]=u;r[tot]=dep;
for(int i=head[u];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(first[y]) continue;/////////搜过就跳过
dfs(y,dep+1);
ver[++tot]=u;r[tot]=dep;
}
}
ll n,m,s,a,b;
ll lg[N];
void init()
{
lg[1]=0;
lg[2]=1;
for(int i=3;i<=N;++i) lg[i]=lg[i/2]+1;
}
ll f[20][N],rec[20][N];
//rec记录的是区间内深度最小值的编号
int main()
{
memset(head,-1,sizeof head);
init();
read(n);read(m);read(s);
for(int i=1;i<=n-1;++i)
{
cin>>a>>b;
add(a,b);
add(b,a);
}
dfs(s,1);//第二个参数是深度,不是爸爸,所以写1而不是-1
for(int i=1;i<=tot;++i)
{
f[0][i]=r[i];
rec[0][i]=ver[i];//分别记录数字内容和对应的深度
}
for(int i=1;i<=lg[tot];++i)
{
for(int j=1;j+(1<<i)-1<=tot;++j)
{
if(f[i-1][j]<f[i-1][j+(1<<(i-1))])
{
f[i][j]=f[i-1][j];
rec[i][j]=rec[i-1][j];
}
else
{
f[i][j]=f[i-1][j+(1<<(i-1))];
rec[i][j]=rec[i-1][j+(1<<(i-1))];
}
}
}
ll l,r;
for(int i=1;i<=m;++i)
{
read(l);read(r);
l=first[l];r=first[r];
if(l>r) swap(l,r);
ll k=lg[r-l+1];
// ll k=0;
// while((1<<k)<=r-l+1) k++;
// k--;
if(f[k][l]<f[k][r-(1<<k)+1]) cout<<rec[k][l]<<endl;
else cout<<rec[k][r-(1<<k)+1]<<endl;
}
return 0;
}