跳转至

D28 基环树 P2607 [ZJOI2008] 骑士_哔哩哔哩_bilibili

基环树

一个图包含n个点n条边,且图中只存在一个环,这样的图被称为基环树(又称环套树)。

基环树比树多了一条边,从而形成了一个环。基环树可以看做以环点为根的一颗颗子树构成。

分三类:

(1)无向树

(2)外向树(每个点只有一条入边)

(3)内向树(每个点只有一条出边)

image.png

思路

类型1:

1.深搜找环。 2.断环成树,对树深搜计算。

类型2:

先处理树(拓扑排序),最后剩下一个环,特殊处理。

例题 #1 [COCI2010-2011#4] DUGOVI

题目描述

在一个小镇上有 \(n\) 位居民,每位居民都**恰好从其他一位**居民那里借了一些钱。

现在到了还债的时间。但问题是每个人都把自己的钱用完了;也就是说任何人都无力偿还债务。这样以来产生了很多冲突。

市长决定解决这个问题。他预想要给一部分居民一些钱,以便于他们用来还债。不过当一部分居民拿到了钱,一系列的连锁反应就开始了。

例如, \(A\) 从市长那里得到了钱。\(A\) 赶忙用这些钱偿还 \(B\) 的债务。\(B\) 也正好偿还 \(C\) 的债务,以此类推。

其中,如果 \(B\) 没有足够的钱来一次还清,那么他会把钱暂时留在自己手里等到钱够了;如果还完债后还有剩余, \(B\) 也会自己留着。(\(B\) 的行为对于任意一个人适用)

另一个例子:如果两个镇上的居民**互欠**对方 \(100\) 美元,那么市长只需要给其中一个人 \(100\) 美元,这两个债务就都解决了。

你需要通过程序来计算出:市长至少要支出多少元给一部分居民才能平息一切债务?

输入格式

输入一行一个整数 \(n\),表示镇上居民的数量。

接下来的 \(n\) 行,每行两个整数 \(A_i,B_i\),表示第 \(i\) 个人欠第 \(A_i\) 个人 \(B_i\) 美元。

居民从 \(1\sim n\) 编号。

输出格式

输出一行一个整数,表示市长最少支出的金额。

数据规模与约定

对于 \(100\%\) 的数据,保证 \(2\le n\le 2\times 10^5\)\(1\le A_i\le n\)\(A_i\neq i\)\(1\le B_i\le 10^4\)

说明

题目译自 COCI2010-2011 CONTEST #4 T5 DUGOVI


先处理树的部分,按入度拓扑排序。然后剩下的是一个环。我们先累计上环的每一个人的固定代价,即他的债务-他的所有收入。然后我们要寻找一个人作为起点,政府给他全额,让他可以开启连锁反应。注意要减去这个人的固定代价。

数组较多,别搞混了。

// Problem: P6486 [COCI2010-2011#4] DUGOVI
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6486
// Memory Limit: 32 MB
// Time Limit: 1000 ms
// Challenger: Erica N
// ----
#include<bits/stdc++.h>

using namespace std;
#define rd read()
#define ull unsigned long long
#define int long long 
#define itn int
#define ps second 
#define pf first

int  read(){
    int x;
    cin>>x;
    return x;
}
#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() {
    cerr << endl;
}
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) {
    for (auto v: a) cerr << v << ' ';
    err(x...);
}
template<typename T, typename... A>
void err(T a, A... x) {
    cerr << a << ' ';
    err(x...);
}
#else
#define dbg(...)
#endif
const int N=3e6+5;
const ull P=137;
const int INF=1e9+7;
/*

策略

如果是一个环,那么给环上的最大边权即可
如果是一个基环树,那么如果一个人的收入不足还,那么需要给钱。并且还需要一个自动资金,给一个人它需要的所有钱。


注意图可能不联通


*/

int fa[N],ind[N],b[N];
int c[N];
int in[N];
int ans;


queue<int> q;
bitset<N> vis;

signed main(){
    int n=rd;
    for(int i=1;i<=n;i++){
        fa[i]=rd;
        ind[fa[i]]++;
        c[i]=b[i]=rd;
        in[fa[i]]+=b[i];
    }

    for(int i=1;i<=n;i++){
        if(!ind[i])q.push(i);
    }


    while(q.size()){
        int x=q.front();
        vis[x]=1;
        q.pop();

        ans+=c[x];
        c[fa[x]]-=b[x];//待还的钱
        c[fa[x]]=max(c[fa[x]],0ll);
        ind[fa[x]]--;
        if(!ind[fa[x]])q.push(fa[x]);
    }

    // cdbg(ans);

    for(int i=1;i<=n;i++){
        if(!vis[i]){
            int mn=INF;
            int cur=i;
            do{
                vis[cur]=1;
                mn=min(mn,c[cur]-max(0ll,b[cur]-in[cur]));//撤销代价
                ans+=max(0ll,b[cur]-in[cur]);
                cur=fa[cur];
            }while(cur!=i);
            // cdbg(ans,mn,in[3]);
            ans+=mn;
        }
    }

    cout<<ans<<endl;


}

例题 #2 [ZJOI2008] 骑士

Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。

最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的 Z 国又怎能抵挡的住 Y 国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。

骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。

战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。

为了描述战斗力,我们将骑士按照 \(1\)\(n\) 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。

输入格式

第一行包含一个整数 \(n\),描述骑士团的人数。

接下来 \(n\) 行,每行两个整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。

输出格式

应输出一行,包含一个整数,表示你所选出的骑士军团的战斗力。

数据规模与约定

对于 \(30\%\) 的测试数据,满足 \(n \le 10\)

对于 \(60\%\) 的测试数据,满足 \(n \le 100\)

对于 \(80\%\) 的测试数据,满足 \(n \le 10 ^4\)

对于 \(100\%\) 的测试数据,满足 \(1\le n \le 10^6\),每名骑士的战斗力都是不大于 \(10^6\) 的正整数。


solution

基本环树思想+树形DP

题解 | [ZJOI2006] 三色二叉树

回顾

树形DP(“没有上司的舞会”)

  1. 不选u,f[u][0] = Σmax(f[v][0], f[v][1])

  2. 选u, f[u][1] = Σf[v][0] + w[u]

请思考

为什么不会形成某个连通块包含多个环的图呢,而仅仅只有可能有连通块是基环树

因为如果只有n-1条边,那么一定是一棵树(因为每一个点至少有一条出边)。此时再加上一条边,就是应该环了。

code

有向图写法(从u最痛恨的人指向u)

/*////////ACACACACACACAC///////////
Code By Ntsc
/*////////ACACACACACACAC///////////
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;


int n,m,ver[N],ans,idx,vis[N],r1,r2,zd[N],f[N][2];
struct node{
    int v,nxt;
}e[N];
int h[N];
void add(int a,int b){
    e[++idx]={b,h[a]};
    h[a]=idx;
}
void find(int u,int rt){//找环,原理很简单:从rt不断走,如果走回了rt就说明有环 
    vis[u]=1;

    for(int i=h[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==rt){
            r1=u,r2=v;return ;//任意找环上2个点即可 
        }
        if(vis[v])continue;//剪枝 
        find(v,rt);
    }
} 
int dfs(int u,int rt){
    f[u][0]=0;f[u][1]=zd[u];
    for(int i=h[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==rt)continue;//保证是树不走环(因为原图是基环树,有一个环) .因为构建的是单向边树,因此不用担心往fa走,无需判定和记录 
        dfs(v,rt);//
        f[u][0]+=max(f[v][0],f[v][1]);
        f[u][1]+=f[v][0];

    }
    return f[u][0];//因为要在断开的边的2端点u1,u2 各进行一次dfs,因此只返回不选u的最大值,避免 u1,u2同时选 
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        int u;
        cin>>zd[i]>>u;
        add(u,i);
    }
    for(int i=1;i<=n;i++){
        if(!vis[i]){//没有被访问,就进去找环 
            r1=r2=0;//记录环的2个端点(即将要断开的边的2端点) 
            find(i,i);//找环  
            if(r1){
                int res1=dfs(r1,r1);//从要断开的边的2端点d其中一个出发 
                int res2=dfs(r2,r2);
                ans+=max(res1,res2);
            }
        }
    }
    cout<<ans<<endl;

    return 0;
}

无向图写法

/*////////ACACACACACACAC///////////
Code By Ntsc
/*////////ACACACACACACAC///////////
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e7+5;

int bk_e[N]; 
int n,m,ver[N],ans,idx=1,vis[N],r1,r2,zd[N],f[N][2];//idx=1便于寻找双向边 
struct node{
    int v,nxt;
}e[N];
int h[N];
void add(int a,int b){
    e[++idx]={b,h[a]};
    h[a]=idx;
}
bool find(int u,int in_e){//找环,原理很简单:如果点v在之前已经被访问过,现在又回到了v,说明有环 
    vis[u]=1;

    for(int i=h[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(i==(in_e^1))continue;//如果当前u->v的边恰好是fa->u的反向边,则说明走回头路了 
        if(!vis[v]){
            if(find(v,i))return 1;//没访问过,进行往下走 
        }else{//如果点v在之前已经被访问过,现在又回到了v,说明有环 
            r1=u,r2=v;bk_e[i]=bk_e[i^1]=1;return 1; 
            //bk_e标记这一条边(包括其反向边)是要被断开的边 ,后面在dfs时就不能走 
        }
    }
    return 0;
}
int dfs(int u,int in_e){
    vis[u]=1;//
    f[u][0]=0;f[u][1]=zd[u];
    for(int i=h[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(i==(in_e^1)||bk_e[i])continue;//保证是树不走环(因为原图是基环树,有一个环) .因为构建的是无向边树,因此需判定和记录入边in_e 
        dfs(v,i);//
        f[u][0]+=max(f[v][0],f[v][1]);
        f[u][1]+=f[v][0];

    }
    return f[u][0];//因为要在断开的边的2端点u1,u2 各进行一次dfs,因此只返回不选u的最大值,避免 u1,u2同时选 
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        int u;
        cin>>zd[i]>>u;
        add(u,i);
        add(i,u); 
    }
    for(int i=1;i<=n;i++){
        if(!vis[i]){//没有被访问,就进去找环 
            r1=r2=0;//记录环的2个端点(即将要断开的边的2端点) 
            find(i,0);//找环  
            if(r1){
                int res1=dfs(r1,0);//从要断开的边的2端点d其中一个出发 
                int res2=dfs(r2,0);
                ans+=max(res1,res2);
            }
        }
    }
    cout<<ans<<endl;

    return 0;
}

很奇怪是是如果这种写法空间开\(N=1e6+5\),最后一个点会RE

例题 #3 [IOI2008] Island

题目描述

你准备浏览一个公园,该公园由 \(N\) 个岛屿组成,当地管理部门从每个岛屿 \(i\) 出发向另外一个岛屿建了一座长度为 \(L_i\) 的桥,不过桥是可以双向行走的。同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。相对于乘船而言,你更喜欢步行。你希望经过的桥的总长度尽可能长,但受到以下的限制:

  • 可以自行挑选一个岛开始游览。

  • 任何一个岛都不能游览一次以上。

  • 无论任何时间,你都可以由当前所在的岛 \(S\) 去另一个从未到过的岛 \(D\)。从 \(S\)\(D\) 有如下方法:

    • 步行:仅当两个岛之间有一座桥时才有可能。对于这种情况,桥的长度会累加到你步行的总距离中。

    • 渡船:你可以选择这种方法,仅当没有任何桥和以前使用过的渡船的组合可以由 \(S\) 走到 \(D\) (当检查是否可到达时,你应该考虑所有的路径,包括经过你曾游览过的那些岛)。

注意,你不必游览所有的岛,也可能无法走完所有的桥。

请你编写一个程序,给定 \(N\) 座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的长度之和的最大值。

输入格式

第一行包含一个整数 \(N\),即公园内岛屿的数目。

随后的 \(N\) 行每一行用来表示一个岛。第 \(i\) 行由两个以单空格分隔的整数,表示由岛 \(i\) 筑的桥。第一个整数表示桥另一端的岛,第二个整数表示该桥的长度 \(L_i\)。你可以假设对于每座桥,其端点总是位于不同的岛上。

输出格式

仅包含一个整数,即可能的最大步行距离。

数据范围

对于 \(100\%\) 的数据,\(2\leqslant N\leqslant 10^6,1\leqslant L_i\leqslant 10^8\)


我们发现渡船只用来从一个联通块到另外一个联通块。所以我们不考虑。

那么现在就是要在一个联通块中找到最长路径。这个联通块一定是一棵基环树。那么就是基环树上的最长路径,用树形dp即可。

具体来说是**求基环树的直径**,那么从环上的所有点开始向树的部分跑最长,记为d。答案要么是\(d_i+d_j+dist(i,j)\),要么是\(d_i+len(环长)-1\)

最难处理的是第一种情况。我们断环为链,那么就是求\(d_i+d_j+dist(i,j)\)的最大值,满足|i-j|<len

// Problem: P4381 [IOI2008] Island
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4381
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Challenger: Erica N
// ----
#include<bits/stdc++.h>

using namespace std;
#define rd read()
#define ull unsigned long long
#define int long long 
#define itn int
#define ps second 
#define pf first

int  read(){
    int x;
    cin>>x;
    return x;
}
#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() {
    cerr << endl;
}
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) {
    for (auto v: a) cerr << v << ' ';
    err(x...);
}
template<typename T, typename... A>
void err(T a, A... x) {
    cerr << a << ' ';
    err(x...);
}
#else
#define dbg(...)
#endif
const int N=1e6+5;
const ull P=137;
const int INF=1e9+7;
/*

策略


*/



struct Node{
    int v,w;
    int id;
};


vector<Node> e[N];


void add(int a,int b,int c,int id){
    e[a].push_back({b,c,id});
    e[b].push_back({a,c,id});
}

int cir[N],cnt;
int d[N],d2[N],d1[N];
int s[N],s2[N];

list<int> ls;

int stk[N],top;
bitset<N> vis,inc;
int len;
int ans;
int hav;

void dfs(int x,int id){

    if(vis[x]){
        if(hav)return ;
        int y;
        do{
            y=stk[top--];
            inc[y]=1;
            cir[++cnt]=y;
        }while(y!=x);
        hav=1;
        return ;
    }
    stk[++top]=x;
    vis[x]=1;
    for(auto v:e[x]){
        if(v.id==id)continue;
        dfs(v.v,v.id);
    }
}
void dfsd(int x,int id){
    for(auto v:e[x]){
        if(inc[v.v])continue;
        if(v.id==id)continue;
        dfsd(v.v,v.id);
        d[x]=max(d[x],d[v.v]+v.w);
    }
}

void dfsl(int x,int rt,int id){
    // cdbg(x); 
    for(auto v:e[x]){
        if(inc[v.v]==1&&v.id!=id){
            len+=v.w;//环长
            d1[v.v]=v.w;//点x在环上的两条临边的长度
            d2[x]=v.w;
            if(v.v!=rt)s[v.v]=s[x]+v.w,dfsl(v.v,rt,v.id);
            break;
        }
    }
}

void solve(int x){
    hav=0;
    cnt=0;
    top=0;
    // inc.reset();
    dfs(x,0);//找环


    int res=0;
    for(int i=1;i<=cnt;i++){
        dfsd(cir[i],0);
        // cdbg(cir[i],d[cir[i]]);
    }

    len=0;
    dfsl(cir[1],cir[1],0);
    // cdbg("Ok",len);
    for(int i=1;i<=cnt;i++){
        res=max(res,d[cir[i]]+len-min(d1[cir[i]],d2[cir[i]]));
    }

    for(int i=1;i<=cnt;i++){
        d2[i]=d2[i+cnt]=d[cir[i]];
        s2[i]=s[cir[i]];
        if(i==1)s2[cnt+1]=len;
        else s2[i+cnt]=s2[i+cnt-1]+s2[i]-s2[i-1];
    }


    for(int i=1;i<=cnt*2;i++){
        // cdbg(s2[i]);
        while(ls.size()&&ls.front()<=i-cnt)ls.pop_front();
        if(ls.size())res=max(res,d2[i]+d2[ls.front()]+s2[i]-s2[ls.front()]);
        while(ls.size()&&d2[ls.back()]-s2[ls.back()]<=d2[i]-s2[i])ls.pop_back();
        ls.push_back(i);
    }

    ans+=res;

    for(int i=1;i<=cnt;i++)inc[cir[i]]=0;

}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n=rd;
    for(int i=1;i<=n;i++){
        int a=rd,b=rd;
        add(i,a,b,i);
    }


    for(int i=1;i<=n;i++){
        if(!vis[i])solve(i);
    }

    cout<<ans<<endl;
}

练习 #1 探险队

计划派遣一支探险队前往邻近的星系。共有 n 名候选人,编号从 1 到 n,需要从中选出探险队成员。组织者希望尽可能多地选出候选人参加探险。

在候选人中进行了一次调查,每个人可以指出一个他不愿意与之一起参加探险的候选人。对于第 i 个候选人,调查结果是一个整数 ai​,表示他不愿意与之一起参加探险的候选人的编号;如果 i 号候选人愿意与任何人一起参加探险,则 ai​=−1。

现在,组织者需要决定谁将参加探险队。决定是这样的:如果某个候选人 i 被选中,并且 ai​≠−1,那么编号为 ai​ 的候选人不能被选中。组织者希望选出最多数量的探险队成员。

编写一个程序,根据给定的候选人调查结果,确定可以派遣的最大候选人数。

输入格式

第一行输入包含一个整数 n (1≤n≤3⋅10^5),表示候选人数。

接下来的 n 行中,每行包含一个整数 ai​(ai​=−1 或 1≤ai​≤n,ai​≠i),表示第 i 个候选人的调查结果。

输出格式

输出一个整数,表示可以派遣的最大候选人数。

详细子任务附加限制及分值如下表所示。

子任务 分值 附加限制 子任务依赖
1 19 n≤20
2 10 a1​=−1,对于 i>1,ai​=i−1
3 15 对于所有 i,ai​<i 2
4 13 1≤n≤2000 1
5 43 1≤n≤3⋅105 1,2,3,4

注意!这里有一个很容易犯的错误。如果用vector写基环树的最大权独立集,并且用记录两端点的写法,考虑如下数据

6
3
3
2
-1
6
1

可能会把3-2识别为环导致2不被统计。此时要判重边(大常数,不建议),或者用**记录边的编号**的方法!。

// Problem: P6223 [COCI2009 Final Exam#1] PODJELA
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6223
// Memory Limit: 125 MB
// Time Limit: 3000 ms
// Challenger: Erica N
// ----
#include<bits/stdc++.h>

using namespace std;
#define rd read()
#define ull unsigned long long
#define int long long 
#define itn int
#define ps second 
#define mp make_pair
#define pf first

int  read(){
    int x;
    cin>>x;
    return x;
}
#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() {
    cerr << endl;
}
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) {
    for (auto v: a) cerr << v << ' ';
    err(x...);
}
template<typename T, typename... A>
void err(T a, A... x) {
    cerr << a << ' ';
    err(x...);
}
#else
#define dbg(...)
#endif
const int N=3e5+5;
const ull P=137;
const int INF=1e9+7;
/*

策略
基环树上的最大权独立集


*/

int a[N];
int ans;


struct Node{
    int v,id;
};
vector<Node> e[N];

void add(int a,int b,int c){
    e[a].push_back({b,c});
    e[b].push_back({a,c});
}

int f[N][2];
int ban;
int l,r;
int banid;

void dfs2(int x,int fa){
    f[x][1]=f[x][0]=0;
//  cdbg(x);
    for(auto v:e[x]){
        if(v.v==fa)continue;
        if(v.v==l)continue;
        if(v.id==banid)continue;
        dfs2(v.v,x);
        f[x][1]+=f[v.v][0];
        if(ban!=v.v)f[x][0]+=max(f[v.v][0],f[v.v][1]);
        else f[x][0]+=f[v.v][0];
    }

    f[x][1]++;
}

bitset<N> vis;

bool dfs(int x,int fa){
    int f=0;
    vis[x]=1;
    for(auto v:e[x]){
        if(v.v==fa)continue;
        if(vis[v.v]){
            f=1;
            l=x,r=v.v;
            banid=v.id;
            continue;
        }
        f|=dfs(v.v,x);
    }
    return f;
}
void solve(int x){
//  cdbg(x);
    if(dfs(x,0)){//判环
//      l,r是得到的环上的两个点
//      cdbg(l,r);
        ban=r;
        int res=0;
        dfs2(l,0);//不选r
        res=max(f[l][1],f[l][0]);       
        ban=0;
        dfs2(l,0);//可选r
        ans+=max(res,f[l][0]);

    }else{
        l=r=0;
        dfs2(x,0);
        ans+=max(f[x][1],f[x][0]);
    }
//      cdbg(ans);

}

signed main(){

    freopen("explore.in","r",stdin);
    freopen("explore.out","w",stdout);

    int n=rd;
    for(int i=1;i<=n;i++){
        a[i]=rd;
//      if(a[i]==i)assert(0);
        if(~a[i])add(i,a[i],i);
    }
    for(int i=1;i<=n;i++){
        if(!vis[i])solve(i);
    }
    cout<<ans<<endl;

    return 0;
}

练习 #2 基环树上lca [POI2012] RAN-Rendezvous

给定一棵内向森林,多次给定两个点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


答案有三种:

  • 不在同一个联通块内,无解

  • 在同一颗子树内,其lca

  • 不在同一颗子树内,那么答案就是两颗子树的根节点的其中之一。注意到环是有向的,所以不能选其它环上的点,更劣。

有点卡常。

// Problem: P3533 [POI2012] RAN-Rendezvous
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3533
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Challenger: Erica N
// ----
#include<bits/stdc++.h>

using namespace std;
#define rd read()
#define ull unsigned long long
// #define int long long 
#define itn int
#define ps second 
#define pf first


#define mp make_pair
#define pii pair<int,int>
#define pb push_back
namespace fastOI{
    #define rd read()
    int read(){
        int xx = 0, ff = 1;
        char ch = getchar();
        while (ch < '0' || ch > '9') {
            if (ch == '-')
                ff = -1;
            ch = getchar();
        }
        while (ch >= '0' && ch <= '9')
          xx = xx * 10 + (ch - '0'), ch = getchar();
        return xx * ff;
    }
    void write(int out) {
        if (out < 0)
            putchar('-'), out = -out;
        if (out > 9)
            write(out / 10);
        putchar(out % 10 + '0');
    }

}using namespace fastOI;


#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() {
    cerr << endl;
}
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) {
    for (auto v: a) cerr << v << ' ';
    err(x...);
}
template<typename T, typename... A>
void err(T a, A... x) {
    cerr << a << ' ';
    err(x...);
}
#else
#define dbg(...)
#endif
const int N=6e5+5;
const ull P=137;
const int INF=1e9+7;
/*

策略


*/


namespace UNI{
    int pa[N];

    int find(int x){
        if(pa[x]==x)return x;
        return pa[x]=find(pa[x]);
    }

    void join(int a,int b){
        int faa=find(a);
        int fbb=find(b);
        if(faa==fbb)return ;
        pa[faa]=fbb;
    }
}using namespace UNI;


int stk[N],cir[N];
int cnt;
int inc[N];
int vis[N];
int top;
int bel[N],cel[N];
int hav;
int clen[N];
vector<int> e[N],g[N];

void dfs(int x){
    //找环
    if(vis[x]){
        if(!hav){

            int y=0;
            do{
                y=stk[top--];
                cir[++cnt]=y;
                inc[y]=1;
            }while(y!=x);
            hav=1;
        }
        return ;
    }
    vis[x]=1;
    stk[++top]=x;

    for(auto v:e[x]){
        dfs(v);
    }
}


void add(int a,int b){
    e[a].pb(b);
}
void addg(int a,int b){
    g[a].pb(b);
}


namespace LCA{
    int dep[N];
    int fa[22][N];
    void dfs2(itn x,int f,int rt){
        dep[x]=dep[f]+1;
        bel[x]=rt;
        for(auto v:g[x]){
            if(v==f)continue;
            if(inc[v])continue;
            fa[0][v]=x;
            for(int i=1;i<=20;i++){
                fa[i][v]=fa[i-1][fa[i-1][v]];
            }

            dfs2(v,x,rt);
        }
    }

    int lca(int a,int b){
        if(dep[a]<dep[b])swap(a,b);
        for(int i=20;~i;i--){
            if(dep[fa[i][a]]>=dep[b])a=fa[i][a];
        }
        if(a==b)return a;
        for(int i=20;~i;i--){
            if(fa[i][b]!=fa[i][a]){
                a=fa[i][a];
                b=fa[i][b];
            }
        }
        return fa[0][a];
    }

    int getDis(int a,int b){
        //one must be anc
        return abs(dep[a]-dep[b]);
    }
}using namespace LCA;


int s[N];
void dfs3(int x,int rt){
    // cdbg(x);
    for(auto v:e[x]){
        if(inc[v]&&v!=rt){
            s[v]=s[x]+1;
            dfs3(v,rt);
        }
    }
}
int getDisC(int a,int b){
    if(s[a]<s[b]){
        return s[b]-s[a];
    }
    return clen[cel[a]]-(s[a]-s[b]);
}


inline void print(int a,int b){
    write(a);
    putchar(' ');
    write(b);
    puts("");
}

signed main(){

    // ios::sync_with_stdio(0);
    // cout.tie(0);
    int n=rd,m=rd;

    for(int i=1;i<=n;i++){
        pa[i]=i;
    }
    for(int i=1;i<=n;i++){
        int a=rd;
        add(i,a);
        addg(a,i);
        join(i,a);
    }

    int tot=0;
    for(int i=1;i<=n;i++){
        if(!vis[find(i)]){ //!!

            tot++;
            cnt=0;
            hav=0;
            dfs(i);
            for(int i=1;i<=cnt;i++){
                cel[cir[i]]=tot;
                dfs2(cir[i],0,cir[i]);
            }

            dfs3(cir[1],cir[1]);
            clen[tot]=cnt;
// cdbg("OK");


        }
    }



    while(m--){
        int a=rd,b=rd;
        if(find(a)!=find(b)){
            puts("-1 -1");
            continue;
        }
        if(bel[a]==bel[b]){
            int anc=lca(a,b);
            print(getDis(a,anc),getDis(b,anc));
        }else{
            itn c1=bel[a],c2=bel[b];
            int x1=getDis(a,c1),y1=getDis(b,c2)+getDisC(c2,c1);
            int x2=getDis(a,c1)+getDisC(c1,c2),y2=getDis(b,c2);
            if(max(x1,y1)==max(x2,y2)){
                if(min(x1,y1)>min(x2,y2))print(x2,y2);
                else if(min(x1,y1)<min(x2,y2))print(x1,y1);
                else{
                    if(x1>=y1)print(x1,y1);
                    else print(y1,x1);
                }
            }else{
                if(max(x1,y1)>max(x2,y2))print(x2,y2);
                else print(x1,y1);

            }
        }
    }


}