代码编织梦想

转载请注明出处:http://blog.csdn.net/jiangshibiao/article/details/23659735

【LCA的线性解法】LCA(最近公共祖先)的问题十分常见。以前我单纯的认为,每次O(N)扫一遍每个节点的深度、再直接暴力求LCA的效率很高——Nlog(N)。但是往往树会退化成链(或者说它不平衡),如果询问次数多的话肯定TLE。离线解法TARJAN(这人好厉害,强连通算法也是他发明的)的效率则是O(N+Q),其中Q是询问个数。

【原理】用到了并查集的思想,每次对于一个点,处理询问中与他有关的一些点,最后链到它的父亲中。递归实现。

【伪代码】

LCA(W)
vist[w]=true;fa[w]=w;
for 所有与w相连的结点c   if  (!vist[c]){
                                                     LCA(c);
                                                     fa[c]=w;
                                                     一定要做完c以后再合并;
                                                              }
for 所有与w相关联的结点c  if  (vist[c])
                                {
                               int u=get(c);
                               记录答案:c和w的LCA是u。
                               }
【应用】前段时间也刷了几道POJ中有关此类的题目。但今天的刷的poj1470的AC历程很辛酸,还是要写一下。

【原题】

Closest Common Ancestors
Time Limit: 2000MS Memory Limit: 10000K
Total Submissions: 14300 Accepted: 4604

Description

Write a program that takes as input a rooted tree and a list of pairs of vertices. For each pair (u,v) the program determines the closest common ancestor of u and v in the tree. The closest common ancestor of two nodes u and v is the node w that is an ancestor of both u and v and has the greatest depth in the tree. A node can be its own ancestor (for example in Figure 1 the ancestors of node 2 are 2 and 5)

Input

The data set, which is read from a the std input, starts with the tree description, in the form: 

nr_of_vertices 
vertex:(nr_of_successors) successor1 successor2 ... successorn 
...
where vertices are represented as integers from 1 to n ( n <= 900 ). The tree description is followed by a list of pairs of vertices, in the form: 
nr_of_pairs 
(u v) (x y) ... 

The input file contents several data sets (at least one). 
Note that white-spaces (tabs, spaces and line breaks) can be used freely in the input.

Output

For each common ancestor the program prints the ancestor and the number of pair for which it is an ancestor. The results are printed on the standard output on separate lines, in to the ascending order of the vertices, in the format: ancestor:times 
For example, for the following tree: 

Sample Input

5
5:(3) 1 4 2
1:(0)
4:(0)
2:(1) 3
3:(0)
6
(1 5) (1 4) (4 2)
      (2 3)
(1 3) (4 3)

Sample Output

2:1
5:5

Hint

Huge input, scanf is recommended.

Source


【大意】给定一棵树的结构和一些询问(x,y),求x和y的LCA。输出每个点它成为多少点对的答案。

【初始代码】

#include<cstdio>
#define N 1005
using namespace std;
char ch;
struct arr{int next,go;}a[N*2];
struct ARR{int NEXT,GO;}A[N*2];
int f[N],x,y,num,n,m,i,ans[N],cnt,CNT,end[N],END[N],root;
bool visit[N],flag[N*2];
inline int Read()
{
  while (ch<'0'||ch>'9') ch=getchar();int s=0;
  while (ch>='0'&&ch<='9') s=s*10+ch-48,ch=getchar();return s;
}
inline void add(int u,int v) {a[++cnt].go=v;a[cnt].next=end[u];end[u]=cnt;}
inline void ADD(int U,int V) {A[++CNT].GO=V;A[CNT].NEXT=END[U];END[U]=CNT;}
inline int get(int u){return f[u]==u?u:f[u]=get(f[u]);}
void tarjan(int k)
{
  f[k]=k;visit[k]=true;
  for (int i=end[k];i>=0;i=a[i].next)
  {
    int go=a[i].go;
    if (!visit[go])
    {
      tarjan(go);f[go]=k;
    }
  }
  for (int i=END[k];i>=0;i=A[i].NEXT)
  {
    int GO=A[i].GO;
    if (visit[GO]&&!flag[i]) ans[get(GO)]++,flag[i]=true,flag[i^1]=true;
  }
}
int main()
{
  scanf("%d",&n);ch=' ';cnt=CNT=-1;
  for (i=1;i<=n;i++) end[i]=END[i]=-1;
  for (i=1;i<=n;i++)
  {
    x=Read();num=Read();if (i==1) root=x;
    while (num)
    {
      num--;y=Read();add(x,y);add(y,x);
    }
  }
  m=Read();
  for (i=1;i<=m;i++)
    x=Read(),y=Read(),ADD(x,y),ADD(y,x);
  tarjan(root);
  for (i=1;i<=n;i++)
    if (ans[i]) printf("%d:%d\n",i,ans[i]);
  return 0;  
}

【悲惨经历】几乎什么问题都被我碰上了。


【注意点】

①给定的是有向边。

②根要自己找。我一开始以为是无向边,这样无法获取根的位置,于是我默认第一个输入的点是根~~

③读入注意一下。(我觉得我的读入优化应该没问题,虽然AC代码中改成了scanf)

④有些时候会重复计算。因为是有向边,我们可以把visit[k]=true移到中间,这样就不会重复计算了。(当然麻烦点也可以,我原来是开边表的,多开一个bool数组判重也可以)

⑤英语渣,没看出来——有多组数据。

⑥因为有多组数据,所以每次要好好的清零。

以上的改好后,我就一直RE,后来去网上找题解,它们说询问要用邻接矩阵来记录。为什么呢?原来可能有多次重复询问(这题目太坑了,竟然没说询问个数)或者询问的很多,邻接矩阵还能记录重复的询问。

最终,A掉了。真是感慨万千啊!

【AC代码】

#include<cstdio>
#include<cstring>
#define N 1005
using namespace std;
char ch;
struct arr{int next,go;}a[N*2];
int f[N],x,y,num,n,m,i,ans[N],cnt,CNT,end[N],END[N],root,fa[N],ask[N][N];
bool visit[N];
inline void add(int u,int v) {a[++cnt].go=v;a[cnt].next=end[u];end[u]=cnt;}
inline int get(int u){return f[u]==u?u:f[u]=get(f[u]);}
void tarjan(int k)
{
  f[k]=k;
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;
    tarjan(go);f[go]=k;
  }
  visit[k]=true;
  for (int i=1;i<=n;i++)
    if (visit[i]&&ask[k][i])
      ans[get(i)]+=ask[k][i];

}
int main()
{
  while (scanf("%d",&n)!=EOF)
  {
    memset(end,0,sizeof(end));
    memset(ask,0,sizeof(ask));
    memset(ans,0,sizeof(ans));
    memset(fa,0,sizeof(fa));
    memset(visit,0,sizeof(visit));
    cnt=0;CNT=0;
    for (i=1;i<=n;i++)
    {
      scanf("%d:(%d)",&x,&num);
      while (num)
      {
        num--;scanf("%d",&y);add(x,y);fa[y]++;
      }
    }
    scanf("%d",&m);
    for (i=1;i<=m;i++)
      scanf(" (%d %d) ",&x,&y),ask[x][y]++,ask[y][x]++;
    for (i=1;i<=n;i++)
      if (fa[i]==0) {root=i;break;}
    tarjan(root);
    for (i=1;i<=n;i++)
      if (ans[i]) printf("%d:%d\n",i,ans[i]);
  }
  return 0;  
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jiangshibiao/article/details/23659735

poj1470 closest common ancestors lca(最近公共祖先)-爱代码爱编程

题意: 给出一棵树和多组询问,然后依次输出以点i为最近公共祖先的询问有多少组。 思路: 很裸得LCA离线算法。 代码(4664K,1547MS): #include <cstdio> #include <cstring> #include <algorithm> #include <iost

poj 1470 lca-targan离线算法-爱代码爱编程

题意: 给一棵树n个节点,然后一些k个询问u,v。 最后输出的是每个询问中被询问的u,v的最近公共祖先和被访问的次数。 解析: 用targan离线处理,每次遇到访问则cnt++。 代码: #include <iostream> #include <cstdio> #include <cstdlib&

[poj 1470]closest common ancestors[离线lca]-爱代码爱编程

题目链接: [POJ 1470]Closest Common Ancestors[离线LCA] 题意分析: 给出多个查询,查询点u和v的最近公共祖先。输出每个点作为最近公共祖先在查询中出现的次数,0次的不输出。 解题思路: 离线LCA,需要用到tarjan。 和普通的tarjan差不多。多了两样东西: 1.u - > v回溯的时候,更新

poj 1470 closest common ancestors tarjan算法求lca-爱代码爱编程

题目:http://poj.org/problem?id=1470 题意:输入一个n,代表树有n个点,从1到n。接下来n行,每行第一个数为树上的点,然后一个数,假设是m吧,其后跟m个数,代表之前的点指向这m个点。然后有大于等于1组的查询,查询它们的LCA,最后输出每个点作为LCA的次数,为0的不输出。 思路:LCA模板题啦,求LCA时直接统

poj1470 closest common ancestors(lca离线优化)-爱代码爱编程

http://poj.org/problem?id=1470 题意:给你点和关系,还有m次询问,求某点是最小公共祖先的次数。 思路:整体思路和poj1330相似,但是这题时间卡的相当紧,听别人说是多组询问。解决办法就是用数组存起来,LCA的时候每处理一个点就处理与该点有关的询问,这样遍历完也就处理完所有询问。保存次数我用的结构体,刚开始想

poj1470closest common ancestors(lca模版)-爱代码爱编程

就是一个板子,sum[lca(u, v)]++就好了。 const int LOGN = 11; const int maxn = 1 << LOGN; int head[maxn], nxt[maxn], p

poj 1470(lca离线算法)_julicliy的博客-爱代码爱编程

/* LCA离线算法: void(int root){ 判断当前节点root是不是要查询集合组中一个点,如果是,那么查看组另一个点 ,如查被标记了,那么找这个点的根节点;就是他们两个的最近的公共祖先; 标记root这个点; LCA(遍历 root下的所有结点) } */ #include <iostream>

poj1470 (closest common ancestors)_小_可_爱_的博客-爱代码爱编程

Write a program that takes as input a rooted tree and a list of pairs of vertices. For each pair (u,v) the pr

poj 1470 lca 公共祖先_iteye_6233的博客-爱代码爱编程

裸题一个 /* ID: CUGB-wwj PROG: LANG: C++ */ #include <iostream> #include <vector> #include <list> #include <map> #include <set> #include <deque&g

tarjan算法求LCA问题解析 + 模板 洛谷P3379——JAVA版-爱代码爱编程

题目链接:传送门:洛谷P3379 关于tarjan算法解决LCA的问题我在网上找了很久,因为它是离线算法的关系,答案输出的顺序总是存在或多或少的问题,网上似乎也没有对着模板题敲这个算法AC的代码,特别是JAVA版本。 前缀知识: 1.并查集,2.dfs,3.邻接表。 算是几个基础的知识点了,很多算法都有引用到,不会的话还是先去学这些吧。