【BZOJ2791】[Poi2012]Rendezvous 倍增

【BZOJ2791】[Poi2012]Rendezvous

Description

给定一个n个顶点的有向图,每个顶点有且仅有一条出边。
对于顶点i,记它的出边为(i, a[i])。
再给出q组询问,每组询问由两个顶点a、b组成,要求输出满足下面条件的x、y:
1. 从顶点a沿着出边走x步和从顶点b沿着出边走y步后到达的顶点相同。
2. 在满足条件1的情况下max(x,y)最小。
3. 在满足条件1和2的情况下min(x,y)最小。
4. 在满足条件1、2和3的情况下x>=y。
如果不存在满足条件1的x、y,输出-1 -1。

Input

第一行两个正整数n和q (n,q<=500,000)。
第二行n个正整数a[1],a[2],...,a[n] (a[i]<=n)。
下面q行,每行两个正整数a,b (a,b<=n),表示一组询问。

Output

输出q行,每行两个整数。

Sample Input

12 5
4 3 5 5 1 1 12 12 9 9 7 1
7 2
8 11
1 2
9 10
10 5

Sample Output

2 3
1 2
2 2
0 1
-1 -1

题解:由于给出的是个基环树森林,所以我们考虑如下几种情况。

1.最终不会走到一个环上,-1。
2.还没走到环上就相遇,那么我们用倍增,当成树上LCA来处理即可。
3.走到环上才相遇,那么相遇点一定是两人刚走到环上时的两个点中的一个,判一下即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int maxn=500010;
int n,m,sum,cnt;
int r[20][maxn],to[maxn],next[maxn],head[maxn],Log[maxn],bel[maxn],pos[maxn],len[maxn],toc[maxn],d[maxn];
queue<int> q;
vector<int> v[maxn];
inline void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
inline int lca(int a,int b)
{
	if(d[a]<d[b])	swap(a,b);
	for(int i=Log[d[a]-d[b]];i>=0;i--)	if(d[r[i][a]]>=d[b])	a=r[i][a];
	if(a==b)	return a;
	for(int i=Log[d[a]];i>=0;i--)	if(r[i][a]!=r[i][b])	a=r[i][a],b=r[i][b];
	return r[0][a];
}
inline bool cmp(int x1,int y1,int x2,int y2)
{
	if(max(x1,y1)!=max(x2,y2))	return max(x1,y1)<max(x2,y2);
	if(min(x1,y1)!=min(x2,y2))	return min(x1,y1)<min(x2,y2);
	return x1>=y1;
}
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+(gc^'0'),gc=getchar();
	return ret*f;
}
int main()
{
	n=rd(),m=rd();
	memset(head,-1,sizeof(head));
	int i,j,u,a,b,x,y,x1,y1,x2,y2;
	for(i=1;i<=n;i++)	r[0][i]=rd(),d[r[0][i]]++;
	for(i=2;i<=n;i++)	Log[i]=Log[i>>1]+1;
	for(j=1;(1<<j)<=n;j++)	for(i=1;i<=n;i++)	r[j][i]=r[j-1][r[j-1][i]];
	for(i=1;i<=n;i++)	if(!d[i])	q.push(i);
	while(!q.empty())
	{
		u=q.front(),q.pop();
		d[r[0][u]]--;
		if(!d[r[0][u]])	q.push(r[0][u]);
	}
	for(i=1;i<=n;i++)	if(d[i]&&!bel[i])
		for(sum++,j=i;!bel[j];j=r[0][j])	pos[j]=++len[sum],bel[j]=sum;
	for(i=1;i<=n;i++)
	{
		if(bel[i])	d[i]=0,toc[i]=i,q.push(i);
		else	add(r[0][i],i);
	}
	while(!q.empty())
	{
		u=q.front(),q.pop();
		for(i=head[u];i!=-1;i=next[i])	d[to[i]]=d[u]+1,toc[to[i]]=toc[u],q.push(to[i]);
	}
	for(i=1;i<=m;i++)
	{
		a=rd(),b=rd();
		if(bel[toc[a]]!=bel[toc[b]])	printf("-1 -1\n");
		else	if(toc[a]==toc[b])
		{
			x=lca(a,b);
			printf("%d %d\n",d[a]-d[x],d[b]-d[x]);
		}
		else
		{
			x=d[a],a=toc[a],y=d[b],b=toc[b];
			x1=x+(pos[b]-pos[a]+len[bel[a]])%len[bel[a]],y1=y;
			x2=x,y2=y+(pos[a]-pos[b]+len[bel[b]])%len[bel[b]];
			if(cmp(x1,y1,x2,y2))	printf("%d %d\n",x1,y1);
			else	printf("%d %d\n",x2,y2);
		}
	}
	return 0;
}//12 5 4 3 5 5 1 1 12 12 9 9 7 1 7 2 8 11 1 2 9 10 10 5 

转载于:https://www.cnblogs.com/CQzhangyu/p/7814389.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值