前言
这道题目非常可爱,甜美,我调了两天总算调出来了。。。。
题目描述
给定一个有 $n $个顶点的有向图,每个顶点有且仅有一条出边。每次询问给出两个顶点 a i 和 b i a_i和 b_i ai和bi,求满足以下条件的 x i x_i xi和 y i y_i yi:
- 从顶点$ a_i$沿出边走 x i x_i xi步与从顶点 b i b_i bi沿出边走 y i y_i yi步到达的顶点相同。
- m a x ( x i , y i ) max(x_i,y_i) max(xi,yi)最小。
- 满足以上条件的情况下 $min(x_i,y_i) $最小。
- 如果以上条件没有给出一个唯一的解,则还需要满足 x i ≥ y i x_i≥y_i xi≥yi.
如果不存在这样的 x i x_i xi和 y i y_i yi,则 x i = y i = − 1 x_i=y_i=−1 xi=yi=−1.
题解
首先,我们可以发现题目所给出的图一定是一个基环树森林,那么我们可以对状态进行分类讨论。
如果 x , y 两点不在同一棵基环树中,那么无论如何它们都不能跳到同一个点,所以输出 -1 ,-1。
如果x,y两点在同一棵基环树中,则考虑它们在不在同一棵子树中。
如果在,那么直接LCA就好了。
如果不在,那么我们先算出两点到子树的根的距离,而且两点一定都会走这段路程,所以现在两个点就在换上了,这时我们发现可以让x走到y,或者y走到x两种情况,所以答案有两种,然后通过题目所给的限制判断就行了。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int Step=20;
int n,m;
vector<int> v[500005];
int col[500005];
int root[500005]; //所有的子树的根节点编号
int tot,rt_tot;
struct Lca{
bool f[500005];
int pre[500005];
int dp[500005][30];
void BFS(int Rt,int k){
queue<int> q;
q.push(Rt);
pre[Rt]=1;
while(!q.empty()){
int x=q.front();
root[x]=k;
q.pop();
for(int i=0;i<v[x].size();i++){
int y=v[x][i];
if(pre[y]){
continue;
}
if(col[x]==col[y]&&col[x]!=0)
continue;
dp[y][0]=x;
pre[y]=pre[x]+1;
for(int j=1;j<=Step;j++){
dp[y][j]=dp[dp[y][j-1]][j-1];
}
q.push(y);
}
}
}
int LCA(int x,int y){
if(pre[x]>pre[y]){
int t=x;
x=y;
y=t;
}
for(int i=Step;i>=0;--i){
if(pre[dp[y][i]]>=pre[x])
y=dp[y][i];
}
if(x==y)
return x;
for(int i=Step;i>=0;--i){
if(dp[x][i]!=dp[y][i]){
x=dp[x][i];
y=dp[y][i];
}
}
return dp[x][0];
}
}t;
int fa[500005]; //父亲
int f[500005]; //所属奇环树编号
int XF[500005]; //在环中的位置
int ZXL[500005]; //环的长度
int HXY[500005]; //根节点
int now[500005];//暂存stack的元素
void DFS(int x,int k,int rt){
f[x]=k;
HXY[x]=rt;
for(int i=0;i<v[x].size();i++){
int y=v[x][i];
if(f[y])
continue;
DFS(y,k,rt);
}
}
void Find(int x){
stack<int> st;st.push(x);
f[x]=++tot;
while(!f[fa[x]]){
x=fa[x];
f[x]=tot;
st.push(x);
}
int nowtot=1;
now[1]=fa[x];
col[fa[x]]=tot;
while(st.size()&&st.top()!=fa[x]){
now[++nowtot]=st.top();
f[st.top()]=0x3f;
col[st.top()]=tot;
st.pop();
}
while(st.size())
f[st.top()]=0,st.pop(); //一定要清零,否则DFS会炸,扇贝我调了2天,妇产科
for(int i=1;i<=nowtot;i++){
DFS(now[i],tot,now[i]);
t.BFS(now[i],++rt_tot),XF[now[i]]=i,ZXL[now[i]]=nowtot;
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d",&fa[i]),v[fa[i]].push_back(i);
for(int i=1;i<=n;i++)
if(!f[i]){
Find(i);
}
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
if(f[x]!=f[y]) //如果不在同一棵基环树内就直接输出-1
printf("-1 -1\n");
else if(root[x]==root[y]){
//如果在同一个子树内
int now=t.LCA(x,y);
x=t.pre[x]-t.pre[now];
y=t.pre[y]-t.pre[now];
printf("%d %d\n",x,y);
}
else{
int X=t.pre[x]-t.pre[HXY[x]],rt_x=HXY[x];
int Y=t.pre[y]-t.pre[HXY[y]],rt_y=HXY[y]; //让两个点先跑到环上
int len=ZXL[rt_x]; //求出这个环的长度
//两种答案
int xx=X+(XF[rt_x]-XF[rt_y]+len)%len; //破环成链
int yy=Y;
int xxx=X;
int yyy=Y+(XF[rt_y]-XF[rt_x]+len)%len;
int ansx,ansy; //比较答案
if(max(xx,yy)<max(xxx,yyy))
ansx=xx,ansy=yy;
else if(max(xx,yy)>max(xxx,yyy))
ansx=xxx,ansy=yyy;
else{
if(min(xx,yy)<min(xxx,yyy))
ansx=xx,ansy=yy;
else if(min(xx,yy)>min(xxx,yyy))
ansx=xxx,ansy=yyy;
else{
if(xx<yy)
swap(xx,yy);
ansx=xx,ansy=yy;
}
}
printf("%d %d\n",ansx,ansy);
}
}
return 0;
}