[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。
输入格式
第一行两个正整数n和k,表示顶点数和询问个数。
接下来一行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≤2000,k≤2000。
对于100%的数据,1≤n≤500,000,1≤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\n",flag.first,flag.second); else printf("%d %d\n",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\n",flag1+flag3,flag2);//比较大小输出答案 else if(max(flag1+flag3,flag2)>max(flag1,flag2+flag4))printf("%d %d\n",flag1,flag2+flag4); else if(min(flag1+flag3,flag2)<min(flag1,flag2+flag4))printf("%d %d\n",flag1+flag3,flag2); else if(min(flag1+flag3,flag2)>min(flag1,flag2+flag4))printf("%d %d\n",flag1,flag2+flag4); else if(flag1+flag3<=flag2)printf("%d %d\n",flag1,flag2+flag4); else printf("%d %d\n",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++