zoukankan      html  css  js  c++  java
  • [BZOJ2791]:[Poi2012]Rendezvous(塔尖+倍增LCA)

    题目传送门


    题目描述

    给定一个有n个顶点的有向图,每个顶点有且仅有一条出边。每次询问给出两个顶点${a}_{i}$${b}_{i}$​​,求满足以下条件的${x}_{i}$${y}_{i}$
        • 从顶点${a}_{i}$沿出边走${x}_{i}$步与从顶点${b}_{i}$​​沿出边走${y}_{i}$步到达的顶点相同。
        • $max({{x}_{i}},{{y}_{i}})$最小。
        • 满足以上条件的情况下$min({{x}_{i}},{{y}_{i}})$最小。
        • 如果以上条件没有给出一个唯一的解,则还需要满足${x}_{i}$${y}_{i}$
    如果不存在这样的${x}_{i}$${y}_{i}$,则${x}_{i}$=${y}_{i}$=−1


    输入格式

    第一行两个正整数nk,表示顶点数和询问个数。
    接下来一行n个正整数,第i个数表示i号顶点出边指向的顶点。
    接下来k行表示询问,每行两个整数${a}_{i}$${b}_{i}$​​


    输出格式

    对每组询问输出两个整数${x}_{i}$${y}_{i}$


    样例

    样例输入:

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

    样例输出:

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


    数据范围与提示

    对于40%的数据,n≤2000k≤2000
    对于100%的数据,1≤n≤500,0001≤k≤500,000


    题解

    正解显然是基环树,但是我还用不六,完了……

    我觉得你可能觉得塔尖和倍增LCA怎么着也不会卡在一起,但是我做到了。

    其实,我只会用塔尖缩点和用倍增求LCA,于是便诞生了这个塔尖和倍增LCA结合在一起的代码。

    然后我发现网上并没有这种解法,所以,推荐你不要直接颓代码,不然容易被教练干……

    言归正转,首先,这张图里肯定有且只有一个环,那么问题就简单多了。

    用并查集记录两个点存不存在这样的${x}_{i}$${y}_{i}$

    通过塔尖找到这个环,并记录这个环的信息,包括大小和哪个点属于这个环。

    之后就是统计答案的过程了。

    如果发现两个点不存在这样的${x}_{i}$和${y}_{i}$,则直接puts("-1 -1")即可。

    如果发现它们在一个环上,那么就直接找到他们的LCA即可。

    如果两个点都不在环上,那么我们就分别求出两个点到环的距离和环上要走的路径,然后比较大小输出答案即可。

    如果其中一个点在环上,另一个点不在,则解法同上,毕竟那个在环上的点到环的距离为0

    建议你不要尝试这种解法,毕竟一开始我以为这就是正解,然后还忽悠旁边的547大佬调了一下午……

    还有就是,这种算法显然常数比较大,时间复杂度不优,建议使用快读,不然会T到飞起。


    代码时刻

    #include<bits/stdc++.h>
    using namespace std;
    struct rec
    {
    	int nxt;
    	int to;
    }e[500001];//存图
    int n,k;
    int head[500001],cnt;
    int dfn[500001],low[500001],sta[500001],ins[500001],c[500001],size[500001],lim[500001],num,tot,top;//塔尖用品
    int fa[500001][30],depth[500001],tr[500001],sum;//LCA用品
    bool vis[500001];
    int f[500001];//并查集
    inline int read(){//记得快读,不然会T
    	int ss=0;char bb=getchar();
    	while(bb<'0' || bb>'9')bb=getchar();
    	while(bb>='0' && bb<='9')ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    	return ss;
    }
    inline void pre_work(){for(register int i=1;i<=n;i++)f[i]=i;}//并查集初始化
    inline int find(register int x){return f[x]==x?x:f[x]=find(f[x]);}//并查集的find
    inline void build(register int x,register int y){if(find(x)!=find(y))f[y]=x;}//并查集将两个点并在一起
    inline void add(register int x,register int y)//建边
    {
    	e[++cnt].nxt=head[x];
    	e[cnt].to=y;
    	head[x]=cnt;
    }
    inline void tarjan(register int x)//塔尖缩点
    {
    	dfn[x]=low[x]=++num;
    	sta[++top]=x;
    	ins[x]=1;
    	for(register int i=head[x];i;i=e[i].nxt)
    		if(!dfn[e[i].to])
    		{
    			tarjan(e[i].to);
    			low[x]=min(low[x],low[e[i].to]);
    		}
    		else if(ins[e[i].to])
    			low[x]=min(low[x],dfn[e[i].to]);
    	if(dfn[x]==low[x])
    	{
    		tot++;
    		int y;
    		int flag=find(sta[top]);
    		do
    		{
    			y=sta[top--];
    			ins[y]=0;
    			c[y]=tot;
    			size[tot]++;//记录大小
    			if(size[tot]>1)lim[flag]=tot;//记录哪个点在这个环上
    		}while(x!=y);
    	}
    }
    inline void dfs(register int x,register int id)//dfs预处理属于哪个部分、深度和父亲
    {
    	vis[x]=1;
    	tr[x]=id;
    	for(register int i=head[x];i;i=e[i].nxt)
    		if(!vis[e[i].to])
    		{
    			fa[e[i].to][0]=x;
    			depth[e[i].to]=depth[x]+1;
    			for(register int j=1;j<=18;j++)
    				fa[e[i].to][j]=fa[fa[e[i].to][j-1]][j-1];
    			if(c[x]==c[e[i].to])dfs(e[i].to,e[i].to);
    			else dfs(e[i].to,id);
    		}
    }
    inline pair<int,int> LCA(register int x,register int y)//求两个点到LCA的距离
    {
    	register int ans1=0,ans2=0;
    	if(depth[x]<depth[y])swap(x,y);
    	for(register int i=18;i>=0;i--)
    		if(depth[fa[x][i]]>=depth[y])
    		{
    			x=fa[x][i];
    			ans1+=(1<<i);
    		}
    	if(x==y)return make_pair(ans1,0);
    	for(register int i=18;i>=0;i--)
    		if(fa[x][i]!=fa[y][i])
    		{
    			x=fa[x][i];
    			y=fa[y][i];
    			ans1+=(1<<i);
    			ans2+=(1<<i);
    		}
    	return make_pair(ans1+1,ans2+1);
    }
    inline int LCA1(register int x,register int y)//求一个点到LCA的距离,用来卡常
    {
    	register int ans=0;
    	if(depth[x]<depth[y])swap(x,y);
    	for(register int i=18;i>=0;i--)
    		if(depth[fa[x][i]]>=depth[y])
    		{
    			x=fa[x][i];
    			ans+=(1<<i);
    		}
    	if(x==y)return ans;
    	for(register int i=18;i>=0;i--)
    		if(fa[x][i]!=fa[y][i])
    		{
    			x=fa[x][i];
    			y=fa[y][i];
    			ans+=(1<<i);
    		}
    	return ans;
    }
    inline void ptinf(register int x,register int y)//输出
    {
    	if(find(x)!=find(y))//不存在这样的x[i],y[i]
    	{
    		puts("-1 -1");
    		return;
    	}
    	if(tr[x]==tr[y])//在一个环上
    	{
    		pair<int,int> flag=LCA(x,y);
    		if(depth[x]>depth[y])printf("%d %d
    ",flag.first,flag.second);
    		else printf("%d %d
    ",flag.second,flag.first);
    		return;
    	}
    	register int flag=find(x),flag1=LCA1(x,tr[x]),flag2=LCA1(y,tr[y]),flag3,flag4;
    	x=tr[x];
    	y=tr[y];
    	if(depth[x]<depth[y])
    	{
    		flag4=depth[y]-depth[x];
    		flag3=size[lim[flag]]-flag4;
    	}
    	else
    	{
    		flag3=depth[x]-depth[y];
    		flag4=size[lim[flag]]-flag3;
    	}//预处理到环的距离和环上要走的路径
    		 if(max(flag1+flag3,flag2)<max(flag1,flag2+flag4))printf("%d %d
    ",flag1+flag3,flag2);//比较大小输出答案
    	else if(max(flag1+flag3,flag2)>max(flag1,flag2+flag4))printf("%d %d
    ",flag1,flag2+flag4);
    	else if(min(flag1+flag3,flag2)<min(flag1,flag2+flag4))printf("%d %d
    ",flag1+flag3,flag2);
    	else if(min(flag1+flag3,flag2)>min(flag1,flag2+flag4))printf("%d %d
    ",flag1,flag2+flag4);
    	else if(flag1+flag3<=flag2)printf("%d %d
    ",flag1,flag2+flag4);
    	else printf("%d %d
    ",flag1+flag3,flag2);
    }
    int main()
    {
    	n=read(),k=read();
    	pre_work();
    	for(register int i=1;i<=n;i++)
    	{
    		int x=read();
    		build(x,i);
    		if(x!=i)add(x,i);
    	}
    	for(register int i=1;i<=n;i++)
    		if(!dfn[i])
    		{
    			depth[i]=1;
    			tarjan(i);
    		}
    	for(register int i=1;i<=tot;i++)
    		if(!vis[find(i)])
    		{
    			depth[find(i)]=1;
    			dfs(find(i),find(i));
    		}
    	for(register int i=1;i<=k;i++)
    	{
    		int x=read(),y=read();
    		ptinf(x,y);
    	}
    	return 0;
    }
    

    rp++

  • 相关阅读:
    【全网最全的博客美化系列教程】文章总目录
    不要再被骗了------QQ盗号原理大揭秘
    努力的孩子运气不会太差,跌宕的人生定当更加精彩
    我的七条人生哲理以及个人学习方法总结
    博客园自定义页面风格设计 后续篇(页面设计模式及代码高亮 鼠标点击效果升级)
    【资料分享】500篇干货解读人工智能新时代
    我的大一生活以及在博客园写博客的这一年
    博客园自定义页面风格设计
    ACM退役记&&回忆录
    留言板
  • 原文地址:https://www.cnblogs.com/wzc521/p/11178433.html
Copyright © 2011-2022 走看看