洛谷P5002 专心OI - 找祖先
程序员文章站
2022-04-25 16:52:39
...
题面
给一节点数n<=10000的树,根为R,询问M<=50000次:对于某一个节点P而言,有多少种节点组合能使P为两点的LCA
分析
看似LCA,实际是组合数学问题,简单一想:
①如果两个点存在于P下两个不同分支,则P必为其LCA
如序偶(u,v),u在subtree1中,而v在subtree2中,则u,v的LCA就是P
这种情况显然是一个组合数学问题:
选定子树后,有size[子树]种节点组合;而在选子树的阶段,无序u,v有种选法,s为子树数量,这可以通过二重循环找出。
同时,显然(v,u)也是一种情况,所以其实有种挑选方式,这个在二重循环中区别不大。
所以找出这些组合后,将其子树size交叉乘积(乘法原理)相加即可。
②一个点是P,另一个点是P的某个后代
这有size[P]-1种选法,同时因为逆序,实际是种
③两个点都是P,仅一种。
处理size,一次dfs即可
查询的次数大于节点数目一些,用预处理打表法即可。
可能存在卡时情况,适当优化就过了。
代码
模数其实无效,大佬们已经发现答案不超过n2
#include<iostream>
#include<vector>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<string>
using namespace std;
#define MOD 1000000007
vector<int>T[10005];
int siz[10005];
int fa[10005];//标记fa,防止重复访问
int a[10005];//打表数组
inline void insert(int u, int v)
{
T[u].push_back(v);
T[v].push_back(u);
}
void dfs(int s)
{
siz[s] = 1;
int cur;
for (int i = 0; i < T[s].size(); i++)
{
cur = T[s][i];
if (fa[s] != cur)
{
fa[cur] = s;
dfs(cur);
siz[s] += siz[cur];
}
}
}
int main()
{
ios::sync_with_stdio(false);
int n, r, m;//N节点,R根,M询问
int u, v;
cin >> n>>r>>m;
for (register int i = 1; i < n; i++)
{
cin >> u >> v;
insert(u, v);
}
dfs(r);
int ans = 0;
for (register int o = 1; o <= n; o++)
{
ans = 0;
u = o;
//其子树siz,两两交叉乘 + (siz[u]-1)*2 其与所有后代组成序偶,以及逆序 + 1 自身和自身
for (int i = 0; i < T[u].size(); i++)
{
if (T[u][i] == fa[u])continue;
for (int j = i+1; j < T[u].size(); j++)
{
if (T[u][j] == fa[u])continue;
ans =(ans+ siz[T[u][i]] * siz[T[u][j]])%MOD;
}
}
ans =(ans+ (siz[u] - 1))%MOD ;
ans = ans*2%MOD;
ans = (ans + 1) % MOD;
a[o] = ans;
}
for (register int i = 0; i < m; i++)
{
cin >> u;
cout << a[u] << endl;
}
return 0;
}
推荐阅读