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

被折叠的 条评论
为什么被折叠?



