To_Heart—题解——POI2012 Rendezvous

前言

这道题目非常可爱,甜美,我调了两天总算调出来了。。。。

题目描述

给定一个有 $n $个顶点的有向图,每个顶点有且仅有一条出边。每次询问给出两个顶点 a i ​ 和 b i a_i​和 b_i aibi,求满足以下条件的 x i x_i xi​和 y i y_i yi​:

  1. 从顶点$ a_i$​沿出边走 x i x_i xi​步与从顶点 b i b_i bi​沿出边走 y i y_i yi步到达的顶点相同。
  2. m a x ( x ​ i ​ , y i ) max(x​_i​,y_i) max(xi,yi)最小。
  3. 满足以上条件的情况下 $min(x_i,y_i​) $最小。
  4. 如果以上条件没有给出一个唯一的解,则还需要满足 x i ≥ y i x_i≥y_i xiyi.

如果不存在这样的 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;
}

猜你喜欢

转载自blog.csdn.net/xf2056188203/article/details/120211076
今日推荐