BZOJ2791/POI2012 Rendezvous

Task
給定一個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。
n,q<=500,000.

Solution
根據題目的條件可以建出一棵基環外向樹,也就是下圖的結構.
這裏寫圖片描述

可以看做一個環和以環上各個節點爲根的樹.那麼對於(x,y)就有以下幾種可能:
1.x,y在不同聯通塊中,無解.
2.x,y在同一棵樹中,例如(a,b).那麼兩點之間的最短路徑是確定的,就是它們分別走向LCA的步數.現在問題就轉化成了求LCA了.
3.x,y在不同的樹中,例如(d,a).那麼它們相遇的路徑有兩種可能:
a->c,d->c或a->e,d->e,只要記錄每個節點所在的樹 的根節點編號和在環上的位置即可.

Code

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=500005;
const int S=20;
int dep[M],ec=0,fa[M],n,q,A[M],head[M],id[M],par[M][S],sz[M],B[M];//一千萬 
int on[M];
inline void rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),c>=48);
}
inline void print(int x){
    if(!x)return ;
    print(x/10);
    putchar((x%10)^48);
}
inline void sc(int x){
    if(x<0){x=-x;putchar('-');}
    print(x);
    if(!x)putchar('0');
}
struct node{
    int to,nex;
}e[M];
int get(int x){
    if(fa[x]!=x)return fa[x]=get(fa[x]);
    return fa[x];
}
void Add_edge(int a,int b){
    e[ec]=(node){b,head[a]};
    head[a]=ec++;
}
void dfs(int p,int x,int d){
    dep[x]=d;
    id[x]=p;
    for(int i=head[x];~i;i=e[i].nex){
        dfs(p,e[i].to,d+1);
    }
}
void pt(int a,int b){
    sc(a);putchar(' ');
    sc(b);putchar('\n');
}
int lca(int a,int b){
    if(dep[a]<dep[b])swap(a,b);
    if(b==id[a])return b;
    int i,step=dep[a]-dep[b];
    for(i=S-1;i>=0;i--){
        if(step&(1<<i))a=par[a][i];
    }
    if(a==b)return a;
    for(i=S-1;i>=0;i--){
        if(par[a][i]!=par[b][i])a=par[a][i],b=par[b][i];
    }
    return par[a][0];
}
int main(){
    int i,j,k,a,b,c;
    memset(head,-1,sizeof(head));
    scanf("%d %d",&n,&q);
    for(i=1;i<=n;i++)fa[i]=i;
    for(i=1;i<=n;i++){
        rd(A[i]);
        int x=get(A[i]);
        if(x==i){//find  a loop
            B[x]=id[x]=x;sz[x]=1;
            on[x]=1;x=A[i];
            while(x!=i){//找到環
                B[x]=i;
                on[x]=++sz[i];
                x=A[x];
            }
        }
        else fa[i]=x;
        par[i][0]=A[i];
    }
    for(i=1;i<=n;i++){
        if(!on[i])Add_edge(A[i],i);
    }
    for(i=1;i<=n;i++){
        if(on[i])dfs(i,i,1);//i是環上的節點,以i爲根遍歷樹
    }
    for(j=1;j<S;j++){
        for(i=1;i<=n;i++)
            par[i][j]=par[par[i][j-1]][j-1];
    }
    while(q--){
        rd(a);rd(b);
        if(B[id[a]]!=B[id[b]]){
            pt(-1,-1);
            continue;
        }
        if(id[a]==id[b]){
            int c=lca(a,b);
            pt(dep[a]-dep[c],dep[b]-dep[c]);
            continue;
        }
        int c=dep[a]-dep[id[a]],d=dep[b]-dep[id[b]];
        int mx1,mx2,siz=sz[B[id[a]]],step=on[id[b]]-on[id[a]];//表示從a走到b
        if(step<0)step+=siz; 
        mx1=max(c+step,d),mx2=max(c,d+siz-step);
        if(mx1<mx2)pt(c+step,d);
        else if(mx2<mx1)pt(c,d+siz-step);
        else {
            int mn1=c+step+d-mx1,mn2=c+d+siz-step-mx2;
            if(mn1>mn2)pt(c,d+siz-step);
            else pt(c+step,d);
        }
    }
    return 0;
}
發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章