HDU 4297 One and One Story 分类讨论, LCA 2012年成都网络赛J题

3 篇文章 0 订阅

题目大意:

就是现在有一个有向图, 每个点出度都是1(只有一条出边), 这个图可以有环, 也可以有自环, 图的点的个数 N <= 500000

然后有K次询问 (K <= 500000), 每次询问给出两个整数u和v表示男主在u房间, 女主在v房间, 现在两个人要走到同一个房间, 需要走多少步

如果不可能到同一个房间, 那么输出-1 -1, 否则如果男主需要走A步女主需要走B步, 那么输出max(A, B)最小的方案, 如果有多种输出min(A, B)最小的方案, 如果依旧有多种, 输出B比较小的方案


大致思路:

首先这个题要注意到图可能被分成多个连通图, 如果男女主在不同的连通部分的话, 是不可能相遇的, 这个连通的性质可以用并查集来维护

如果男女主在同一个连通的块上, 那么考虑到每个点出度都是1, 这个部分最多只有一个环从这个环上不会有其他的出边, 只会有入边, 那么就是一个环周围有很多分支的树, 换上的点是这些树的根节点, 于是由于树上的点一定都能走到环上, 而环上的点都是连通的, 于是男女主一定可以相遇

那么如果男女猪脚都在环上, 我们在找出环的同时给环上的点标号, 就可以轻松找出环上点的最近距离(可以想到此时男女主角一定是一个人站着不动另外一个去找这个人, 两个人走的路的max最小)

如果男女有一个在树上一个在环上, 那么需要先从树走到环上, 然后就和两个人在环上一样的道理了

如果男女都在树上但不是同一棵子树(就是环上同一个点分出来的分支), 就需要都走到环上, 接下来和上面一样了

如果两个人都在同一个子树上, 两个人当然是走到他们的最近公共祖先, 于是用ST表来维护一下就行了

考虑到所有的子树的结点树总和不超过50W, 将这个森林的树的ST表写成一个就行, 不用担心内存问题


代码注意一下细节就好了, 其实不是很难, 就是要注意分类讨论就行


代码如下:

Result  :  Accepted     Memory  :  39008 KB     Time  :  1107 ms

/*
 * Author: Gatevin
 * Created Time:  2015/7/31 14:04:11
 * File Name: Sakura_Chiyo.cpp
 */
#pragma comment(linker, "/STACK:16777216")  
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

#define maxn 500010

int nex[maxn];
int rootLoop[maxn];
//---------------LCA
vector<int> G[maxn];
int st[2*maxn + 1][20];
int fa[maxn], dfn[maxn], pos[maxn];
int sum;
void add(int x)
{
    st[++sum][0] = x;
    pos[x] = sum;
    return;
}

void dfs(int now, int ro)
{
    for(int i = 0, sz = G[now].size(); i < sz; i++)
    {
        int u = G[now][i];
        if(u == fa[now]) continue;
        dfn[u] = dfn[now] + 1;
        fa[u] = now;
        add(now);
        dfs(u, ro);
    }
    rootLoop[now] = ro;//指向环上的根, 而dfn代表代打环上的根的距离
    add(now);
    return;
}

int Min(int u, int v)
{
    return dfn[u] < dfn[v] ? u : v;
}

int lca(int u, int v)
{
    u = pos[u], v = pos[v];
    if(u > v) swap(u, v);
    int t = log(v - u + 1.) / log(2.);
    return Min(st[u][t], st[v - (1 << t) + 1][t]);
}

//----------------
int du[maxn];
int father[maxn];
int vis[maxn];
int loop[maxn];
int loopLength[maxn];

int find(int x)
{
    return x == father[x] ? x : father[x] = find(father[x]);
}

void Union(int x, int y)
{
    int fx = find(x);
    int fy = find(y);
    if(fx != fy)
        father[fx] = fy;
    return;
}

int N, K;

vector <int> root;

void init()
{
    memset(rootLoop, -1, sizeof(rootLoop));
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= N; i++) G[i].clear();
    root.clear();
    for(int i = 1; i <= N; i++)
    {
        if(loop[i] && loop[nex[i]]) continue;//环上的边
        G[nex[i]].push_back(i);
        if(loop[nex[i]] && !vis[nex[i]])
        {
            root.push_back(nex[i]);//子树根节点
            vis[nex[i]] = 1;
        }
    }
    //for(int i = 0, sz = root.size(); i < sz; i++)
    //    cout<<root[i]<<" ";
    //cout<<endl;
    //cout<<"ttt"<<endl;
    sum = 0;
    for(int i = 0, sz = root.size(); i < sz; i++)
        dfn[root[i]] = 0, fa[root[i]] = -1, dfs(root[i], root[i]);
    //cout<<"sdsds"<<endl;
    for(int j = 1; (1 << j) <= sum; j++)
        for(int i = 1; i <= sum - (1 << j) + 1; i++)
            st[i][j] = Min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    return;
}

void answer(int x1, int y1, int x2, int y2)
{
    if(max(x1, y1) < max(x2, y2))
    {
        printf("%d %d\n", x1, y1);
        return;
    }
    if(max(x1, y1) > max(x2, y2))
    {
        printf("%d %d\n", x2, y2);
        return;
    }
    if(min(x1, y1) < min(x2, y2))
    {
        printf("%d %d\n", x1, y1);
        return;
    }
    if(min(x1, y1) > min(x2, y2))
    {
        printf("%d %d\n", x2, y2);
        return;
    }
    if(y1 > y2)
        printf("%d %d\n", x2, y2);
    else printf("%d %d\n", x1, y1);
    return;
}


int main()
{
    //freopen("data.in", "r", stdin);
    while(~scanf("%d %d", &N, &K))
    {
        memset(du, 0, sizeof(du));
        for(int i = 0; i <= N; i++) father[i] = i;
        for(int i = 1; i <= N; i++)
        {
            int tmp;
            scanf("%d", &tmp);
            Union(i, tmp);//这两个是连在一起的, Union多个分离的图
            nex[i] = tmp;//i的唯一出口
            du[tmp]++;//入度+1
        }
        memset(vis, 0, sizeof(vis));
        memset(loop, 0, sizeof(loop));
        memset(loopLength, 0, sizeof(loopLength));
        for(int i = 1; i <= N; i++) if(!vis[find(i)])
        {
            //从i开始向前找环
            //vis[find(i)] = 1;
            int now = i;
            vis[now] = 1;
            while(!vis[nex[now]])
            {
                now = nex[now];
                vis[now] = 1;
            }//接下来nex[now]走过了
            int nn = nex[now];
            int cnt = 1;
            if(nn == nex[nn])
            {
                loop[nn] = 1;//一个点的自环
                loopLength[nn] = 1;
            }
            else
            {
                while(nn != now)//多个点的环
                {
                    loop[nn] = cnt++;
                    nn = nex[nn];
                }
                loop[nn] = cnt++;
                loopLength[now] = cnt - 1;
                nn = nex[now];
                while(nn != now)
                {
                    loopLength[nn] = cnt - 1;
                    nn = nex[nn];
                }//再转一圈确定环的大小
            }
            vis[find(i)] = 1;
        }
        //for(int i = 1; i <= N; i++)
        //    if(loop[i]) cout<<i<<" ";
        //cout<<endl;
        //cout<<"hehe"<<endl;;
        //以上找到了所有的环, 用loop[v]非零标记了他们
        //接下来处理LCA以及每个分支的起始环上的父亲
        init();
        
        int x, y;
        while(K--)
        {
            scanf("%d %d", &x, &y);
            if(find(x) != find(y))//不在同一个连通上
            {
                puts("-1 -1");
                continue;
            }
            if(loop[x] && loop[y])//都在环上
            {
                if(loop[x] < loop[y])
                {
                    int xy = loop[y] - loop[x];
                    int yx = loopLength[x] - xy;
                    if(xy <= yx) printf("%d 0\n", xy);
                    else printf("0 %d\n", yx);
                    continue;
                }
                else
                {
                    int yx = loop[x] - loop[y];
                    int xy = loopLength[x] - yx;
                    if(yx < xy) printf("0 %d\n", yx);
                    else printf("%d 0\n", xy);
                    continue;
                }
            }
            if(loop[x] && !loop[y])//x在环上, y在枝上
            {
                int ly = rootLoop[y];//y的根在环上的点
                int dy = dfn[y];//y到环上的距离
                if(loop[x] < loop[ly])
                {
                    int xy = loop[ly] - loop[x];
                    int yx = loopLength[x] - xy;
                    answer(xy, dy, 0, dy + yx);//前面一个和后面一个选择一种
                    continue;
                }
                else
                {
                    int yx = loop[x] - loop[ly];
                    int xy = loopLength[x] - yx;
                    answer(xy, dy, 0, dy + yx);
                    continue;
                }
            }
            if(loop[y] && !loop[x])//y在环上, x在枝上
            {
                int lx = rootLoop[x];
                int dx = dfn[x];
                if(loop[lx] < loop[y])
                {
                    int xy = loop[y] - loop[lx];
                    int yx = loopLength[lx] - xy;
                    answer(xy + dx, 0, dx, yx);
                    continue;
                }
                else
                {
                    int yx = loop[lx] - loop[y];
                    int xy = loopLength[lx] - yx;
                    answer(xy + dx, 0, dx, yx);
                    continue;
                }
            }
            if(!loop[x] && !loop[y])//都在枝上
            {
                if(rootLoop[x] == rootLoop[y])//在同一棵树上, 找LCA
                {
                    int z = lca(x, y);
                    int dx = -dfn[z] + dfn[x];
                    int dy = -dfn[z] + dfn[y];
                    printf("%d %d\n", dx, dy);
                    continue;
                }
                //不在一棵树上求环上点的距离
                int lx = rootLoop[x];
                int ly = rootLoop[y];
                int dx = dfn[x], dy = dfn[y];
                if(loop[lx] < loop[ly])
                {
                    int xy = loop[ly] - loop[lx];
                    int yx = loopLength[lx] - xy;
                    answer(xy + dx, dy, dx, yx + dy);
                    continue;
                }
                else
                {
                    int yx = loop[lx] - loop[ly];
                    int xy = loopLength[lx] - yx;
                    answer(xy + dx, dy, dx, dy + yx);
                    continue;
                }
            }
        }
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值