【题解】Acwing392 会合
\(\text{Solution:}\)
简单题……确实
这是一个基环树森林上找两点换上最短移动方案的题。慢慢考虑其性质。
- 每个点有且仅有一条出边
这意味着这一定是 内向基环树森林 。
还有更重要的意义:环有顺序!
- 求两个点移动到一个公共点上
首先考虑两个点共同在一棵树内的情况,也就是它们两个不跨环。
这个时候我们发现,由于树有方向,所以它们只能向上跳,这也意味着答案就是 \(LCA\) 与它们深度的差。
那么两个点不在同一棵基环树中呢?直接输出 -1 -1
,这也是唯一的无解情况。
那么,如果它们需要跨环,我们就先让他们跳到环上考虑。
引理:环上点移动最优情况下一定只会移动一个点。
证明:题目中要求了使 \((x,y)\) 的最大值最小值均最小,意味着我们不能做多余的移动。而两个点如果同时移动显然会无故增加次数,因为环是单向的。
那么剩下就是考虑如何计算并分类了。
关于计算环上的跳跃,考虑 按照顺序 来对每个点进行标号,这样就可以直接做差得到距离了。注意有两个距离,一个是正向直接跳,另一个是让另一个点绕一圈跳回来。
然后按照题目所说分类讨论即可。但是有一些情况需要注意:
-
环是有方向的,所以在找环的时候 必须 用有向图来找。
-
打标记需要全部打上,所以这个时候我们选择用一张无向图来做。
-
会出现自环什么的情况,但是自然做就不会错。
-
有向图找环不需要判父亲!!!!
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int head[N],tot,n,f[N][20],q,H[N],Tot;
struct E{int nxt,to;}e[N<<1],edge[N<<1];
inline int Max(int x,int y){
return x>y?x:y;
}
inline int Min(int x,int y){
return x<y?x:y;
}
inline void link(int u,int v){
e[++tot]=(E){head[u],v};
head[u]=tot;
}
inline void Link(int u,int v){
edge[++Tot]=(E){H[u],v};
H[u]=Tot;
}
vector<int>Cir[N];
int num,st[N],top,dep[N],vis[N];
int to[N],Tg[N],trg[N],rk[N],Vis[N],ts[N];
void Get(int node,int x){
int i;
for(i=top;i&&st[i]!=x;--i)Cir[node].push_back(st[i]),st[i]=0;
Cir[node].push_back(x);st[i]=0;
}
void Find_Circle(int x,int fa,int node,int tg){
vis[x]=1;st[++top]=x;Tg[x]=tg;
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
if(vis[j]&&Cir[node].empty()){
Get(node,j);
}
if(!vis[j])Find_Circle(j,x,node,tg);
}
st[top]=0;
--top;
}
void Dtag(int x,int fa,int tg){
Vis[x]=1;Tg[x]=tg;
for(int i=H[x];i;i=edge[i].nxt){
int j=edge[i].to;
if(j==fa)continue;
if(Vis[j])continue;
Dtag(j,x,tg);
}
}
void dfs(int x,int fa,int tg){
dep[x]=dep[fa]+1;
f[x][0]=fa;trg[x]=tg;
for(int i=1;i<20;++i)f[x][i]=f[f[x][i-1]][i-1];
for(int i=H[x];i;i=edge[i].nxt){
int j=edge[i].to;
if(j==fa)continue;
if(vis[j])continue;
dfs(j,x,tg);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=19;~i;--i)if(f[x][i]&&dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
for(int i=19;~i;--i)if(f[x][i]&&f[y][i]&&f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("392.out","w",stdout);
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i){
scanf("%d",&to[i]);
link(i,to[i]);
// link(to[i],i);?
Link(i,to[i]);
Link(to[i],i);
// printf("(%d %d)\n",i,to[i]);
}
for(int i=1;i<=n;++i)
if(!vis[i]&&!Vis[i]){
top=0;
num++;
Find_Circle(i,0,num,num);
Dtag(i,0,num);
if(Cir[num].empty())Cir[num].push_back(i);
}
for(int i=1;i<=n;++i)vis[i]=0;
// puts("Circle:");
// for(int i=1;i<=num;++i){
// printf("%d:\n",i);
// for(auto v:Cir[i])printf("%d ",v);
// puts("");
// }
// puts("End.");
for(int i=1;i<=num;++i)
for(auto v:Cir[i])
vis[v]=1;
for(int i=1;i<=num;++i)
for(auto v:Cir[i])
dfs(v,0,v);
for(int i=1;i<=num;++i){
reverse(Cir[i].begin(),Cir[i].end());
int Cirsiz=Cir[i].size();
for(int j=0;j<Cirsiz;++j){
rk[Cir[i][j]]=j+1;
}
}
// for(int i=1;i<=n;++i)printf("%d ",rk[i]);
// puts("");
// for(int i=1;i<=n;++i)printf("%d ",dep[i]);
// puts("");
// for(int i=1;i<=n;++i)printf("%d ",vis[i]);
// puts("");
// for(int i=1;i<=n;++i)printf("%d ",Tg[i]);
// puts("");
// for(int i=1;i<=n;++i)printf("%d ",trg[i]);
// puts("");
for(int qq=1;qq<=q;++qq){
int a,b;
scanf("%d%d",&a,&b);
if(Tg[trg[a]]!=Tg[trg[b]]){
// printf("(%d %d)\n",Tg[a],Tg[b]);
// printf("(%d %d)\n",trg[a],trg[b]);
// printf("(%d %d)\n",Tg[trg[a]],Tg[trg[b]]);
puts("-1 -1");
continue;
}
if(trg[a]==trg[b]){
int L=LCA(a,b);
int xx=dep[a]-dep[L];
int yy=dep[b]-dep[L];
printf("%d %d\n",dep[a]-dep[L],dep[b]-dep[L]);
assert(xx>=0);assert(yy>=0);
continue;
}
int x=dep[a]-1,y=dep[b]-1;
assert(x>-1);
assert(y>-1);
int cirnode=Tg[a];
int cirsiz=Cir[cirnode].size();
int posx=trg[a];
int posy=trg[b];
int cirposx=rk[posx];
int cirposy=rk[posy];
assert(cirposx<=cirsiz);
assert(cirposy>=1&&cirposy<=cirsiz);
int dy=0,dx=0;
if(cirposx<cirposy){
dx=cirposy-cirposx;
dy=cirsiz-dx;
}
else{
dy=cirposx-cirposy;
dx=cirsiz-dy;
}
assert(dx>=0);
assert(dy>=0);
int nx=x+dx;
int ny=y+dy;
assert(nx>=0);
assert(ny>=0);
//(x,ny)(nx,y)
int mx1=Max(x,ny);
int mx2=Max(nx,y);
// printf("(%d %d)\n(%d %d)\n",nx,y,x,ny);
if(mx1!=mx2){
if(mx1>mx2)printf("%d %d\n",nx,y);
else printf("%d %d\n",x,ny);
continue;
}
else{
int mi1=Min(x,ny);
int mi2=Min(nx,y);
if(mi1!=mi2){
if(mi1>mi2)printf("%d %d\n",nx,y);
else printf("%d %d\n",x,ny);
continue;
}
printf("%d %d\n",nx,y);
continue;
}
}
return 0;
}