hihoCoder #1067 : 最近公共祖先·二 [ 离线LCA tarjan ]

传送门:

#1067 : 最近公共祖先·二

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

上上回说到,小Hi和小Ho用非常拙劣——或者说粗糙的手段山寨出了一个神奇的网站,这个网站可以计算出某两个人的所有共同祖先中辈分最低的一个是谁。远在美国的他们利用了一些奇妙的技术获得了国内许多人的相关信息,并且搭建了一个小小的网站来应付来自四面八方的请求。

但正如我们所能想象到的……这样一个简单的算法并不能支撑住非常大的访问量,所以摆在小Hi和小Ho面前的无非两种选择:

其一是购买更为昂贵的服务器,通过提高计算机性能的方式来满足需求——但小Hi和小Ho并没有那么多的钱;其二则是改进他们的算法,通过提高计算机性能的利用率来满足需求——这个主意似乎听起来更加靠谱。

于是为了他们第一个在线产品的顺利运作,小Hi决定对小Ho进行紧急训练——好好的修改一番他们的算法。

而为了更好的向小Ho讲述这个问题,小Hi将这个问题抽象成了这个样子:假设现小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

题解:

离线LCA, tarjan

tarjan算法的步骤是(当dfs到节点u时):
1. 在并查集中建立仅有u的集合,设置该集合的祖先为u
2. 对u的每个孩子v:
   2.1 tarjan之
   2.2 合并v到父节点u的集合,确保集合的祖先是u
3. 设置u为已遍历
4. 处理关于u的查询,若查询(u,v)中的v已遍历过,则LCA(u,v)=v所在的集合的祖先

结果:Accepted      提交时间:2015-05-15 16:17:22

1067 最近公共祖先·二 AC G++ 800ms 35MB 1分钟前 查看
  1 #include <cstdio>
  2 #include <cstring>
  3 #include <iostream>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <cctype>
  7 #include <vector>
  8 #include <cmath>
  9 #include <map>
 10
 11 #define ll long long
 12
 13 using namespace std;
 14
 15 const int N = 100005;
 16 const int M = 105;
 17 const ll mod = 1000000007;
 18
 19 int n,m;
 20 int tot;
 21 map<string,int> mp;
 22 int f[N];
 23 char s1[N],s2[N];
 24 vector<int> G[N];
 25 int vis[N];
 26
 27 struct PP
 28 {
 29     int next;
 30     int index;
 31     PP(int tn,int ti)
 32     {
 33         next = tn;
 34         index = ti;
 35     }
 36 };
 37
 38 vector<PP> query[N];
 39
 40 string name[N];
 41 string ans[N];
 42
 43 int find(int x)
 44 {
 45     return f[x] == x ? f[x] : f[x] = find(f[x]);
 46 }
 47
 48 void merge(int x,int y)
 49 {
 50     int a=find(x);
 51     int b=find(y);
 52     if(a==b) return;
 53     f[b]=a;
 54 }
 55
 56 void ini()
 57 {
 58     tot=0;
 59     int i;
 60     int x,y;
 61     for(i=0;i<=n;i++){
 62         G[i].clear();
 63         query[i].clear();
 64         f[i]=i;
 65     }
 66     for(i=1;i<=n;i++){
 67         scanf("%s%s",s1,s2);
 68         if(mp.count(s1)==0){
 69             tot++;
 70             mp[s1]=tot;
 71             x=tot;
 72             name[tot]=s1;
 73         }
 74         else{
 75             x=mp[s1];
 76         }
 77         if(mp.count(s2)==0){
 78             tot++;
 79             mp[s2]=tot;
 80             y=tot;
 81             name[tot]=s2;
 82         }
 83         else{
 84             y=mp[s2];
 85         }
 86
 87         G[x].push_back(y);
 88         G[y].push_back(x);
 89     }
 90
 91     scanf("%d",&m);
 92     for(i=1;i<=m;i++){
 93         scanf("%s%s",s1,s2);
 94         x=mp[s1];
 95         y=mp[s2];
 96         query[x].push_back(PP(y,i));
 97         query[y].push_back(PP(x,i));
 98     }
 99 }
100
101 void tarjan(int u,int fa)
102 {
103     int i;
104     int v;
105     //printf(" u=%d fa=%d\n",u,fa);
106     for(i=0;i<G[u].size();i++){
107         v=G[u][i];
108         if(v==fa) continue;
109         tarjan(v,u);
110     }
111     int index;
112     vis[u]=1;
113
114     for(i=0;i<query[u].size();i++){
115         v=query[u][i].next;
116         index=query[u][i].index;
117        // printf("  i=%d v=%d index=%d\n",i,v,index);
118         if(vis[v]==0) continue;
119         ans[index]=name[find(v)];
120     }
121     f[u]=fa;
122 }
123
124 void solve()
125 {
126     memset(vis,0,sizeof(vis));
127     tarjan(1,-1);
128 }
129
130 void out()
131 {
132     int i;
133     for(i=1;i<=m;i++){
134         cout<<ans[i]<<endl;
135     }
136 }
137
138 int main()
139 {
140     //freopen("data.in","r",stdin);
141     //scanf("%d",&T);
142     //for(int ccnt=1;ccnt<=T;ccnt++){
143     while(scanf("%d",&n) != EOF) {
144         ini();
145         solve();
146         out();
147     }
148     return 0;
149 }
时间: 2024-08-06 16:00:00

hihoCoder #1067 : 最近公共祖先·二 [ 离线LCA tarjan ]的相关文章

hihoCoder#1067 最近公共祖先&#183;二

原题地址 超时.超内存都碰到了..最后还是参考了这篇博文才勉强AC 需要注意: 1. 肯定是树而不是森林,而且树的根节点一定是第一个出现的名字,所以不需要再去找哪个是根了.这样可以节省一部分内存. 2. 用并查集路径压缩的方法维护并查集结构即可,当查找的时候再压缩,不需要每次染黑节点的时候都压缩.这样可以节省一部分时间. 3. 字符串都转成id再做. 4. 能静态申请内存就尽量用静态内存,比如数组,STL少用,尤其是map,效率极低. 代码: 1 #include <iostream> 2 #

hihocoder 1067 最近公共祖先&#183;二 并查集+stl

题目链接: hihocoder1067 题解思路: 面对10^5个 名字和10^5条询问,肯定要用到特殊的方法: 1.把所有的询问先存下来,然后再遍历一次整棵树得到所有答案 2.遍历的过程中   查询含当前节点的 所有询问,然后找到询问中的另一个节点:查看另一个节点的状态. 如果另一个节点未访问过,接下来处理: 如果另一个节点正在被访问(子节点未访问完),则答案就是这个节点 如果另一个节点已被访问过,则答案是它的正在被访问的根节点 3. 第二点的状态需要并查集处理 未遍历时所有节点标记为未访问

hihoCoder_#1067_最近公共祖先&#183;二(LCA模板)

#1067 : 最近公共祖先·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上上回说到,小Hi和小Ho用非常拙劣--或者说粗糙的手段山寨出了一个神奇的网站,这个网站可以计算出某两个人的所有共同祖先中辈分最低的一个是谁.远在美国的他们利用了一些奇妙的技术获得了国内许多人的相关信息,并且搭建了一个小小的网站来应付来自四面八方的请求. 但正如我们所能想象到的--这样一个简单的算法并不能支撑住非常大的访问量,所以摆在小Hi和小Ho面前的无非两种选择: 其一是购买更为昂

最近公共祖先问题(LCA)模板

最近公共祖先问题(LCA)是求一颗树上的某两点距离他们最近的公共祖先节点,由于树的特性,树上两点之间路径是唯一的,所以对于很多处理关于树的路径问题的时候为了得知树两点的间的路径,LCA是几乎最有效的解法. 首先是LCA的倍增算法.算法主体是依靠首先对整个树的预处理DFS,用来预处理出每个点的直接父节点,同时可以处理出每个点的深度和与根节点的距离,然后利用类似RMQ的思想处理出每个点的 2 的幂次的祖先节点,这就可以用 nlogn 的时间完成整个预处理的工作.然后每一次求两个点的LCA时只要对两个

hiho一下 第十五周——最近公共祖先&#183;二(Trajan,离线LCA)

题目连接 http://hihocoder.com/problemset/problem/1067 题目大意 就是一棵树求任意两个节点的最近公共祖先. 算法描述 在题目的提示里面有比较详细的解释.这里就不多说了.这种算法的时间复杂度是O(n+q). 在算法的实现上也有一些技巧,在参考了一些代码后写了一个比较精简的Trajan_LAC算法. #include <bits/stdc++.h> using namespace std; typedef long long LL; const int

LCA(最近公共祖先)——离线 Tarjan 算法

一.梳理概念 定义:对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 通俗地讲,最近公共祖先节点,就是两个节点在这棵树上深度最大的公共的祖先节点,即两个点在这棵树上距离最近的公共祖先节点. 提示:父亲节点也是祖先节点,节点本身也是它的祖先节点. 给出一棵树,如图所示: 由上面的定义可知:3和5的最近公共祖先为1,5和6的最近公共祖先为2,2和7的最近公共祖先为2, 6和7的最近公共祖先为4. 二.繁文缛节 注意注意注意!!!尚

hicocoder1067 最近公共祖先&#183;二(tarjan算法)

tarjan算法是处理最近公共祖先问题的一种离线算法. 算法思路: 先将所有的询问搜集起来. 然后对树进行dfs,在dfs的过程中对节点进行着色.当到达某个节点x的时候,给x着色为灰色,离开x的时候,着色为黑色. 当到达x并将其着色为灰色后,处理与x相关联的所有询问: (这里有一个显然的事实:所有的灰色节点都是x的祖先) (1)若询问的另一个节点y是灰色,那么该询问的结果为y: (2)若询问的另一个节点y是黑色,那么询问结果应为离y最近的y的灰色祖先(因为所有灰色节点都是x的祖先,所以离y最近的

[ACM] hihocoder 1062 最近公共祖先&#183;一 (一般做法)

描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中,但这是为什么呢? "为什么呢?"小Hi如是问道,在他的观察中小Ho已经沉迷这个网站一周之久了,甚至连他心爱的树玩具都弃置一边. "嘿嘿,小Hi,你快过来看!"小Ho招呼道. "你看,在这个对话框里输入我的名字,在另一个对话框里,输入你的名字,再点这个查询按钮,就可以查出来--什么!我们居然有同一个祖祖祖祖祖爷爷?" "诶,真是诶--这个网

[hiho 15]最近公共祖先 二

题目描述 这次使用离线算法来解决最近公共祖先的问题. 离线算法可以一遍 dfs 处理完所有的查询,因而需要把查询全部储存起来. 具体的 dfs 过程是: 所有节点最初标记为白色,第一次经过该节点时,将其染成灰色,第二次经过该节点时(即离开该节点时)将其染成黑色. 在 dfs 的某个状态下,白色代表未访问的节点,黑色代表已经访问完该节点为根整个子树,灰色代表正在访问该节点为根的子树. 可以在每个节点处查询与该节点相关的所有查询,每个查询对应有另一个节点X(可能是自身): 如果X是白色,说明还未访问