P5002 专心OI - 找祖先【题解】

本文详细介绍了如何使用邻接表和深度优先搜索(DFS)解决一道与最近公共祖先(LCA)相关的OI问题。代码实现包括DFS函数和查询函数,通过存储每个节点的深度和子节点信息,对所有可能的询问预先计算答案并存储。最后,对于输入的查询,直接返回预计算的答案。博客强调了在处理此类问题时,线性递归和记忆化搜索是有效的策略,并提供了完整的C++代码示例。

题目链接:P5002 专心OI - 找祖先

这是一道既和 LCA 有关,又可以称得上和 LCA 没有多少关系的题。

这道题中的数据范围告诉我们,存图要用邻接表(矩阵表示很悲伤),所以先把它的代码给码上:

//init

const int N = 1001000;
const int MOD = 1e9 + 7;

struct Edge{
    int to , nxt;
}a[N];
int cnt , head[N];

void Add( int u , int v )
{
    cnt ++;
    a[cnt].to = v;
    a[cnt].nxt = head[u];
    head[u] = cnt;
}

看到“多次询问”样子的字眼,我们不难想到线性递归,或者记忆化搜索之类的东西。不过对于前者,不少题目需要慎重使用,实际上把数据直接下传就可以了,然而对于这道题而言是一个不可多得的妙解;而对于后者(的思想),我们更加可以考虑使用。毕竟这样子可以避免许多毒瘤测试点 WA 掉

对此,我更倾向于把所有可能询问的数都存数组,随问随调哦。
(写题解之前我看到题解区许多 dalao 也是这样子做的)

对于大多数题目,我们可以开一个 momery 数组表示搜索过了的数所映射的答案,而对于这道被允许直接把所有数搜一遍的题,我们开一个 ask 数组来表示答案。当树与 LCA 碰撞时避免不了产生 dep 这个数组表示深度;为了方便递归儿子数,我们开一个数组 son 来表示他的儿子数。(当然,为了增强代码的可读性,个人认为最好把数组名改成 sun_num 或者 son_sum 云云)

这道题对于卡常没有太大要求,数组大小可以凭心情而定夺。(指的是测评机的心情)


先说主函数。

下面是代码:


signed main()
{
    cin >> n >> r >> m;
    for(int i = 1,x,y;i <= n - 1;i ++)
    {
        cin >> x >> y;
        Add( x , y ) , Add( y , x );
    }
    dfs( r , r );
    for(int i = 1;i <= n;i ++)
    {
    	ask[i] = query(i);
    }
    for(int i=1,x;i <= m;i ++)
    {
        cin >> x;
        cout << ask[x] << endl;
    }
    return 0;
}

聪明的你从第一个单词就看出了,笔者开了 long long,虽说理论上 int 也能过,但我们毕竟要养成好的习惯嘛。

读入不必多说,注意是无向图就好。

之后的函数 dfs( num_1 , num_2 ) ,指的是 num_1 的父亲是 num_2 ,进而递归出这整棵树每个点的深度和儿子数,以上也是此函数的预期;随后我们枚举每一个数,进行询问( query ),询问的过程也就是该函数的预期。

此后,我们就只需要构造函数了。


① dfs( num_1 , num_2 );

上文已经提及,二者对于父节点和子节点,我们暂时用 x 和 fa 来表示;一个子节点,它自身必定有至少一个子节点(就是它本身),其深度比它的父亲大 1 ,然后是相当常规的向下枚举递归,不在多做解释。

代码:

void dfs( int x , int fa )
{
    son[x] = 1;
    dep[x] = dep[fa] + 1;
    for(int e = head[x];e;e = a[e].nxt)
    {
        int v = a[e].to;
        if( v == fa ) continue;
        dfs( v , x );
        son[x] += son[v] % MOD;
    }
}

② query( num );

这里借用了线段树函数里常用的单词 quary ,也是表示查询。定义 size_subtree 为该点的子树大小,size_arrived 表示本次遍历到子树的大小;枚举的同时需要保障数据在范围之内(不能越界,否则不可能有它的儿子),然后计算答案,最后别忘了取模。

代码:

int query( int x )
{
    int size_subtree , size_arrived;
    int ans = size_subtree = son[x];
    for(int e = head[x];e;e = a[e].nxt)
    {
        int v = a[e].to;
        if( dep[v] < dep[x] ) continue;
        size_arrived = son[v];
        ans += (size_subtree - size_arrived ) * size_arrived;
    }
    return ans % MOD;
}

最终代码:

#include <iostream>
#define int long long

using namespace std;

const int N = 1001000;
const int MOD = 1e9 + 7;
struct Edge{
    int to , nxt;
}a[N];
int n , m , r , cnt;
int ask[N] ,head[N] , memory[N];
int son[N] , dep[N];

void Add( int u , int v )
{
    cnt ++;
    a[cnt].to = v;
    a[cnt].nxt = head[u];
    head[u] = cnt;
}

void dfs( int x , int fa )
{
    son[x] = 1;
    dep[x] = dep[fa] + 1;
    for(int e = head[x];e;e = a[e].nxt)
    {
        int v = a[e].to;
        if( v == fa ) continue;
        dfs( v , x );
        son[x] += son[v] % MOD;
    }
}

int query( int x )
{
    int size_subtree , size_arrived;
    int ans = size_subtree = son[x];
    for(int e = head[x];e;e = a[e].nxt)
    {
        int v = a[e].to;
        if( dep[v] < dep[x] ) continue;
        size_arrived = son[v];
        ans += (size_subtree - size_arrived ) * size_arrived;
    }
    return ans % MOD;
}

signed main()
{
    cin >> n >> r >> m;
    for(int i = 1,x,y;i <= n - 1;i ++)
    {
        cin >> x >> y;
        Add( x , y ) , Add( y , x );
    }
    dfs( r , r );
    for(int i = 1;i <= n;i ++)
    {
    	ask[i] = query(i);
    }
    for(int i=1,x;i <= m;i ++)
    {
        cin >> x;
        cout << ask[x] << endl;
    }
    return 0;
}

码字不易,学OI也不易,最后祝大家 NOIP-2021 RP++。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值