hdu One and One Story tarjan缩点+rmq+LCA

http://acm.hdu.edu.cn/showproblem.php?pid=4297

题意:

给出n个点的有向图,每个点的出度均为1.有m个询问,每个询问两个数(u,v),表示两个人一个在u一个在v。

对于每个询问,请你选择一个点P使得u、v均能到达P。设u到达P需要A步,v到达P需要B步。求一个P使得max(A,B)最小?

若答案不唯一,输出min(A,B)最小的;若答案还不唯一,输出A>=B的。(不存在P的A=B=-1)

思路:

这个有向图很特别,由于每个节点只有一条出边,所以如果形成一个环的话就只能有指向这个环的边,同时一个子图内最多存在一个环,tarjan搞掉环,
建反图虚拟根节点转换成一棵树,根节点如果是环的环用带关系的并查集维护,然后lca->rmq,在线得到答案。

 

话说这题太恶心了,高了好久才出来.......

View Code
#pragma comment (linker , "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define MIN(a , b) ((a) < (b) ? (a) : (b))
#define MAX(a , b) ((a) > (b) ? (a) : (b))

#define ll __int64
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 100007
#define N 500007
using namespace std;
//freopen("din.txt","r",stdin);

struct node{
    int v;
    int next;
}edge[N<<1];
int head[N],ct;

int dfn[N],low[N],stack[N],belong[N],cnt[N];
int top,idx,tot;
bool isinS[N],vt[N];

int indeg[N],f[N],dis[N],up[N];
int E[N << 1],D[N << 1],R[N],dep[N],dp[N << 1][20];

int out[N],pow2[32];

int n,m;



void add(int u,int v){
    edge[ct].v = v;
    edge[ct].next = head[u];
    head[u] = ct++;
}
void tarjan(int u){
    dfn[u] = low[u] = ++idx;
    isinS[u] = true;
    stack[top++] = u;
    for (int i = head[u]; i != -1; i = edge[i].next){
        int v = edge[i].v;
        if (dfn[v] == -1){
            tarjan(v);
            low[u] = min(low[u],low[v]);
        }
        else if (isinS[v]){
            low[u] = min(low[u],dfn[v]);
        }
    }
    int v;
    if (dfn[u] == low[u]){
        do{
            v = stack[--top];
            isinS[v] = false;
            belong[v] = tot;
            cnt[tot]++;
        }while (u != v);
        tot++;
    }
}
int find(int x){
    if (x == f[x]) return f[x];
    int tmp = f[x];
    f[x] = find(tmp);
    dis[x] += dis[tmp];
    return f[x];
}
void makeEage(){
    int i;
    CL(head,-1); ct = 0;
    for (i = 1; i <= n; ++i){
        dfn[i] = low[i] = -1;
        isinS[i] = false;
        cnt[i] = 0;
    }
    idx = 0;
    tot = 1;
    //首先正向建图
    for (i = 1; i <= n; ++i){
        scanf("%d",&out[i]);
        add(i,out[i]);
    }
    //tarjan缩点
    for (i = 1; i <= n; ++i){
        if (dfn[i] == -1){
            top = 0;
            tarjan(i);
        }
    }

    CL(head,-1); ct = 0;
    for (int i = 0; i <= n; ++i){
        f[i] = i;
        dis[i] = 0;
        indeg[i] = 0;
        up[i] = -1;
    }
    //重新建立逆向图
    for (i = 1; i <= n; ++i){
        int u = belong[out[i]];
        int v = belong[i];
        if (u != v){
            add(u,v);
            indeg[v]++;
            if (cnt[u] > 1){//如果父节点是环就只想他们
                up[v] = out[i];
            }
        }
        else{//如果是环里的点并查集维护
            int x = find(out[i]);
            int y = find(i);
            if (x != y){
                f[y] = x;
                dis[y] = dis[out[i]] + 1;
            }
        }
    }
    //添加虚拟节点
    for (i = 1; i < tot; ++i){
        if (indeg[i] == 0){
            add(0,i);
        }
    }
}

//dfs E R D dep;
void dfs(int st,int h){
    dep[st] = h;
    E[++top] = st;
    D[top] = h;
    R[st] = top;
    for(int i = head[st];i != -1;i = edge[i].next){
        if(up[edge[i].v] == -1){
            up[edge[i].v] = up[st];
        }
        dfs(edge[i].v , h+1);
        E[++top] = st;
        D[top] = h;
    }
    return;
}
int Min(int i,int j){
    if (D[i] < D[j]) return i;
    else return j;
}
int bound;
//rmq的初始化
void init_rmq()
{
    for (int i = 0; i < 30; ++i) pow2[i] = 1<<i;
    for(int i = 1;i <= top; ++i){
        dp[i][0] = i;
    }
    for(int j = 1; pow2[j] <= top; ++j){
        for(int i = 1; (i + pow2[j] - 1) <= top; ++i){
                dp[i][j] = Min(dp[i][j-1],dp[i + (1 << (j-1))][j-1]);
        }

    }
    return;
}

int rmq(int l,int r){
    int d = log((double)(r - l + 1)) / log(2.0);
    return Min(dp[l][d],dp[r - pow2[d] + 1][d]);
}

void solve()
{
    dfs(0,0);
    init_rmq();
    int u,v;
    
    while(m--)
    {
        scanf("%d%d",&u,&v);
        if(u == v)
        {
            puts("0 0");
            continue;
        }
        int x = belong[u];
        int y = belong[v];
        int lca ;//= askrmq(bj[x] , bj[y]);
        if (R[x] <= R[y]) lca = E[rmq(R[x],R[y])];
        else lca = E[rmq(R[y],R[x])];
        if(lca == 0){//若果是0说明分别在两棵树上,不能到达
            puts("-1 -1");
            continue;
        }
        else if(cnt[lca] == 1){//如果是1说明在一棵树上,不存在公共祖先为环
            printf("%d %d\n",dep[x] - dep[lca],dep[y] - dep[lca]);
            continue;
        }
        //公共祖先为环的情况
        int dx,dy,dxy,dyx;

        dx = dep[x] - dep[lca];
        dy = dep[y] - dep[lca];

        u = (up[x] == -1) ? u : up[x];
        v = (up[y] == -1) ? v : up[y];
        find(u),find(v);
        if(dis[u] < dis[v]){
            dxy = cnt[lca] + dis[u] - dis[v];
            dyx = dis[v] - dis[u];
        }
        else{
            dxy = dis[u] - dis[v];
            dyx = cnt[lca] - dxy;
        }


        if(MAX(dx+dxy , dy) < MAX(dx , dy+dyx)) printf("%d %d\n",dx+dxy , dy);

        else if(MAX(dx+dxy , dy) > MAX(dx , dy+dyx)) printf("%d %d\n",dx , dy+dyx);
        else
        {
            if(MIN(dx+dxy , dy) < MIN(dx , dy+dyx)) printf("%d %d\n",dx+dxy , dy);
            else if(MIN(dx+dxy , dy) > MIN(dx , dy+dyx)) printf("%d %d\n",dx , dy+dyx);
            else
            {
               if (dx+dxy > dy) printf("%d %d\n",dx+dxy , dy);
               else printf("%d %d\n",dx , dy+dyx);
            }
        }
    }
    return;
}
int main(){
    //freopen("din.txt","r",stdin);
    while (~scanf("%d%d",&n,&m)){
        makeEage();
        solve();
    }
    return 0;
}

 

 

 

 

posted @ 2012-09-22 09:13  E_star  阅读(216)  评论(0编辑  收藏  举报