洛谷P5002 专心OI - 找祖先

题面

给一节点数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有 C s 2 C_{s}^2 种选法,s为子树数量,这可以通过二重循环找出。
同时,显然(v,u)也是一种情况,所以其实有 2 C s 2 2C_{s}^2 种挑选方式,这个在二重循环中区别不大。
所以找出这些组合后,将其子树size交叉乘积(乘法原理)相加即可。

②一个点是P,另一个点是P的某个后代
这有size[P]-1种选法,同时因为逆序,实际是 2 ( s i z e [ 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;
}
发布了32 篇原创文章 · 获赞 0 · 访问量 1179

猜你喜欢

转载自blog.csdn.net/engineoid/article/details/104255115