Luogu P3379 【模板】最近公共祖先(LCA)

  这就是一道很朴素的LCA题。

  算法应该主流的有三种:DFS序+RMQ;倍增;Tarjan;

  其中前面两个是在线算法,O(n log n),后一个是离线的,复杂度也是线性的O(n+q)。

  所以对于这道题n,q都偏大的情况下还是选择了Tarjan(后两种我不会)

  简单说一下Tarjan的思想,对整个树进行一次DFS,vis数组记录这个点是否被记录。

  对于每一个搜到的点,先查询所有要问的与它有关的点(就比如要求i,j,在搜到i或j的时候就要找到j或i),如果这个点被访问过了他们的LCA就是这个点的father(用并查集实现)。

  Why——? 

  因为如果这两个点都访问过了,那它们肯定在同一棵子树中(以根节点也算子树),那么由于j先被访问,因此它的祖先就是i,j的LCA。

  最后注意一下这是一棵树,所以总边数是n-1。

  提示一下在大牛分站交会有O2优化。

  CODE

#include<cstdio>
#include<vector>
using namespace std;
const int N=500005;
vector <int> a[N],q[N],num[N];
int father[N],ans[N],n,m,x,y,i,root;
bool vis[N];
inline void read(int &x)
{
    x=0; char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
inline void write(int x)
{
    if (x/10) write(x/10);
    putchar(x%10+'0');
}
inline int getfather(int k) { return father[k]==k?k:father[k]=getfather(father[k]); }
inline void LCA(int k)
{
    vis[k]=1;
    for (int i=0;i<q[k].size();++i)
    {
        int x=q[k][i];
        if (vis[x]) ans[num[k][i]]=getfather(x);
    }
    for (int i=0;i<a[k].size();++i)
    {
        int x=a[k][i];
        if (!vis[x]) LCA(x),father[x]=k;
    }
}
int main()
{
    read(n); read(m); read(root);
    for (i=1;i<=n;++i)
    father[i]=i;
    for (i=1;i<=n-1;++i)
    {
        read(x); read(y);
        a[x].push_back(y); a[y].push_back(x);
    }
    for (i=1;i<=m;++i)
    {
        read(x); read(y);
        q[x].push_back(y); num[x].push_back(i);
        q[y].push_back(x); num[y].push_back(i);
    }
    LCA(root);
    for (i=1;i<=m;++i)
    write(ans[i]),putchar('\n');
    return 0;
}

 

posted @ 2018-01-05 13:20  空気力学の詩  阅读(214)  评论(2编辑  收藏  举报