欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

洛谷P5002 专心OI - 找祖先

程序员文章站 2022-04-25 16:52:39
...

题面

给一节点数n<=10000的树,根为R,询问M<=50000次:对于某一个节点P而言,有多少种节点组合能使P为两点的LCA

分析

看似LCA,实际是组合数学问题,简单一想:
洛谷P5002 专心OI - 找祖先
①如果两个点存在于P下两个不同分支,则P必为其LCA
如序偶(u,v),u在subtree1中,而v在subtree2中,则u,v的LCA就是P
这种情况显然是一个组合数学问题:
选定子树后,有size[子树]种节点组合;而在选子树的阶段,无序u,v有Cs2C_{s}^2种选法,s为子树数量,这可以通过二重循环找出。
同时,显然(v,u)也是一种情况,所以其实有2Cs22C_{s}^2种挑选方式,这个在二重循环中区别不大。
所以找出这些组合后,将其子树size交叉乘积(乘法原理)相加即可。

②一个点是P,另一个点是P的某个后代
这有size[P]-1种选法,同时因为逆序,实际是2(size[P]1)2(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;
}
相关标签: