hihocoder#1069 : 最近公共祖先·三(DFS序列+线段树)

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB
描述

上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的“最近公共祖先”网站,但是很快这样一个离线算法就出现了问题:如果只有一个人提出了询问,那么小Hi和小Ho很难决定到底是针对这个询问就直接进行计算还是等待一定数量的询问一起计算。毕竟无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的。

那么问题就来了,如果每次计算都只针对一个询问进行的话,那么这样的算法事实上还不如使用最开始的朴素算法呢!但是如果每次要等上很多人一起的话,因为说不准什么时候才能够凑够人——所以事实上有可能要等上很久很久才能够进行一次计算,实际上也是很慢的!

“那到底要怎么办呢?在等到10分钟,或者凑够一定数量的人两个条件满足一个时就进行运算?”小Ho想出了一个折衷的办法。

“哪有这么麻烦!别忘了和离线算法相对应的可是有一个叫做在线算法的东西呢!”小Hi笑道。

小Ho面临的问题还是和之前一样:假设现在小Ho现在知道了N对父子关系——父亲和儿子的名字,并且这N对父子关系中涉及的所有人都拥有一个共同的祖先(这个祖先出现在这N对父子关系中),他需要对于小Hi的若干次提问——每次提问为两个人的名字(这两个人的名字在之前的父子关系中出现过),告诉小Hi这两个人的所有共同祖先中辈分最低的一个是谁?

提示:最近公共祖先无非就是两点连通路径上高度最小的点嘛!
输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2~N+1行,每行分别描述一对父子关系,其中第i+1行为两个由大小写字母组成的字符串Father_i, Son_i,分别表示父亲的名字和儿子的名字。

每组测试数据的第N+2行为一个整数M,表示小Hi总共询问的次数。

每组测试数据的第N+3~N+M+2行,每行分别描述一个询问,其中第N+i+2行为两个由大小写字母组成的字符串Name1_i, Name2_i,分别表示小Hi询问中的两个名字。

对于100%的数据,满足N<=10^5,M<=10^5, 且数据中所有涉及的人物中不存在两个名字相同的人(即姓名唯一的确定了一个人),所有询问中出现过的名字均在之前所描述的N对父子关系中出现过,且每个输入文件中第一个出现的名字所确定的人是其他所有人的公共祖先

输出

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:他们的所有共同祖先中辈分最低的一个人的名字。

样例输入
4
Adam Sam
Sam Joey
Sam Micheal
Adam Kevin
3
Sam Sam
Adam Sam
Micheal Kevin
样例输出
Sam
Adam
Adam
思路:模版题。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e6+10;
map<string,int>ma;
map<int,string>ans;
string fa,son;
struct lenka
{
    int l,r,an;
}A[MAX<<2];
struct EDG
{
    int to,next;
}ed[MAX];
int head[MAX],tot=0,m=0,all=0,n;
int p[MAX],num[MAX],f[MAX],d[MAX];
void add(int x,int y)
{
    ed[tot].to=y;
    ed[tot].next=head[x];
    head[x]=tot++;
}
void dfs(int fa,int dep)
{
    p[fa]=all;
    d[all]=dep;
    num[all++]=fa;
    for(int i=head[fa];i!=-1;i=ed[i].next)
    {
        int nex=ed[i].to;
        dfs(nex,dep+1);
		p[fa]=all;
		d[all]=dep;
		num[all++]=fa;
    }
}
void build(int k,int l,int r)
{
    A[k].l=l,A[k].r=r;
    if(l==r){A[k].an=l;return;}
    build(2*k,l,(l+r)/2);
    build(2*k+1,(l+r)/2+1,r);
    if(d[A[2*k].an]>d[A[2*k+1].an])A[k].an=A[2*k+1].an;
    else A[k].an=A[2*k].an;
}
int ask(int k,int l,int r)
{
    if(l==A[k].l&&r==A[k].r)return A[k].an;
    if(r<=A[2*k].r)return ask(2*k,l,r);
    else if(l>=A[2*k+1].l)return ask(2*k+1,l,r);
    int dl=ask(2*k,l,A[2*k].r);
    int rl=ask(2*k+1,A[2*k+1].l,r);
    return d[dl]>d[rl]?rl:dl;
    
}
int main()
{
    memset(f,0,sizeof f);
    memset(head,-1,sizeof head);
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        cin>>fa>>son;
        if(ma[fa]==0)ma[fa]=++m,ans[m]=fa;
        if(ma[son]==0)ma[son]=++m,ans[m]=son;
        f[ma[son]]=ma[fa];
        add(ma[fa],ma[son]);
    }
    for(int i=1;i<=m;i++)if(f[i]==0)dfs(i,0);
    build(1,0,all-1);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        cin>>fa>>son;
        int y=ask(1,min(p[ma[fa]],p[ma[son]]),max(p[ma[fa]],p[ma[son]]));
        cout<<ans[num[y]]<<endl;
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值