POJ 1470 Closest Common Ancestors LCA_Tarjan

Closest Common Ancestors
Time Limit: 2000MS Memory Limit: 10000K
Total Submissions: 18141 Accepted: 5807

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


题意:有向图,输入一个点,括号里输入有几个儿子,后面输入儿子,输入完毕,输入询问查询公共祖先,最后输出每个点的 作为公共祖先的次数,次数为0的不输出

开始以为是简单的LCA问题,然后RE,然后看了下1470下面的discuss和其他的题解,才发现有好几个坑。

1.询问有多次,有说询问要开50W的
2.重复询问相同的 数据

我用vector存的询问,不知道为什么会re,然后看别人用的邻接矩阵,也用邻接矩阵,A了,虽然还是不知道为什么vector会re
学到一个点是vector开出内存后进行clear操作不会释放内存(不要问我怎么知道的,mle血淋淋的教训),至于好的解决办法我去找了下  传送门,大概意思就是开一个临时vector变量,进行swap就行了。

我的更详解的Tarjan理解: 传送门

CODE:
#include"stdio.h"
#include"iostream"
#include"algorithm"
#include"string.h"
#include"vector"
using namespace std;
const int maxn = 1000+10;

struct node           ///邻接表
{
    int to;
    int next;
}e[maxn];
int n,m;
int top;              ///邻接表边的条数
int ans[maxn];        ///次数
int head[maxn];       ///邻接表头结点
int fa[maxn];         ///并查集的父亲结点
int in[maxn];         ///定点入度,用来寻找根节点
int vis[maxn];        ///Tarjan里面进行标记
int flag[maxn][maxn]; ///一个询问可能出现多次,正向或者反向的询问都只能加一次,所以需要标记
int query[maxn][maxn];///询问次数

void INIT()           ///初始化
{
    top = 0;
    for(int i = 0;i < maxn;i++)
    {
        ans[i] = vis[i] = in[i] = 0;
        head[i] = -1;
        fa[i] = i;
    }
    memset(query,0,sizeof query);
    memset(flag,0,sizeof flag);
}

void add(int u,int v)   ///邻接表加边
{
    e[top].to = v;
    e[top].next = head[u];
    head[u] = top++;
}

int Find(int x)
{
    if(x != fa[x])
        fa[x] = Find(fa[x]);
    return fa[x];
}

void Union(int x,int y)  ///并查集联合
{
    int fx = Find(x);
    int fy = Find(y);
    fa[fy] = fx;
}

void Tarjan(int u)
{
    vis[u] = 1;
    for(int i = head[u];i != -1;i = e[i].next)
    {
        int t = e[i].to;
        Tarjan(t);
        Union(u,t);
    }
    for(int i = 1;i <= n;i++)
    {
        if(vis[i] && query[u][i] && !flag[u][i])
        ans[Find(i)] += query[u][i],flag[u][i] = flag[i][u] = 1;   ///双向加标记,一种询问只能加一次
    }
}

int main(void)
{
    while(scanf("%d",&n) != EOF)
    {
        INIT();
        for(int i = 1;i <= n;i++)
        {
            int h,sons;
            scanf("%d:(%d)",&h,&sons);
            for(int j = 1;j <= sons;j++)
            {
                int son;
                scanf("%d",&son);
                add(h,son);
                in[son]++;
            }
        }
        scanf("%d",&m);
        for(int i = 1;i <= m;i++)
        {
            int a,b;
            scanf(" (%d %d)",&a,&b);
            query[a][b]++;
            query[b][a]++;   ///双向加
        }
        for(int i = 1;i <= n;i++)
            if(in[i] == 0)
                Tarjan(i);
        for(int i = 1;i <= n;i++)
        {
            if(ans[i])
            {
                printf("%d:%d\n",i,ans[i]);
            }
        }
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值