【树形DP+逆元】求树上不为祖孙的集合数 洛谷P5007

题目描述

给定一棵以 1 为根的有根树,定义树的一个毒瘤集为一个集合,并且集合中任意两个元素之间不存在祖先与后代关系。

定义一个毒瘤集的毒瘤指数为集合内所有元素的价值之和

要求给定树的所有毒瘤集的毒瘤指数之和,答案对 100000007 取模。

但这个问题太难了,所以我们考虑化简。

因为点的编号跟它毒瘤指数密切相关,所以我们将会再给出一个整数 T,T = 1 表示 i 号点的毒瘤指数为 i,T = 0,表示所有点的毒瘤指数都是 1

输入输出格式

输入格式:

 

第一行两个整数 n、T,表示这棵树有 n 个节点。

接下来 n -1 行,每行两个整数 x 和 y,表示有一条边,连接 x 和 y。

 

输出格式:

 

输出一个整数,表示答案。

 

输入输出样例

输入样例#1: 复制

5 0
1 2
2 3
2 4
1 5

输出样例#1: 复制

16

说明

样例解释:

10 个集合分别为 {1},{2},{3},{4},{5},{2,5},{3,4},{3,5},{3,4,5},{4,5}\{1\},\{2\},\{3\},\{4\},\{5\},\{2,5\},\{3,4\}, \{3,5\},\{3,4,5\},\{4,5\}{1},{2},{3},{4},{5},{2,5},{3,4},{3,5},{3,4,5},{4,5}

数据范围与约定

本题采用多测试点捆绑测试

对于 30 % 的部分分,n <= 15

另外 20 % 的部分分,n <= 1000000,T = 0

对于 100 % 的数据,n <= 1000000, T <= 1

为了方便你理解题意,下面给出毒瘤集的数学定义:

设一个毒瘤集为AAA,则

∀ i ∈ A\forall~i~\in~A∀ i ∈ A,不存在一个点jjj,使得jjj在从iii到根节点的简单路径上,且 j ∈ A~j~\in~A j ∈ A。其中 i,j ∈ V~i,j~\in~V i,j ∈ V,VVV为树的点集。

 

 

逆元:a / b % mod = a * b ^ (mod - 2) % mod;

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const ll mod = 100000007;
const int mn = 1000010;

int cnt;
int to[2 * mn], nx[2 * mn], fr[2 * mn];
void addedge(int a, int b)
{
	to[cnt] = b;
	nx[cnt] = fr[a];
	fr[a] = cnt++;
}

ll Pow(ll a, ll b)
{
	ll ans = 1;
	while (b)
	{
		if (b & 1)
			ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

ll val[mn], sum[mn];	// sum[i] i的子树构成的集合总数, val[i] i的子树集合的值
void dfs(int u, int fa)
{
	sum[u] = 1;
	for (int i = fr[u]; i != -1; i = nx[i])
	{
		if (to[i] == fa)
			continue;

		int v = to[i];
		dfs(v, u);
		sum[u] = sum[u] * sum[v] % mod;		// 父节点子树集合总数 = 子节点们的集合总数乘积
	}

	for (int i = fr[u]; i != -1; i = nx[i])
	{
		if (to[i] == fa)
			continue;

		int v = to[i];
		val[u] = (val[u] + val[v] * sum[u] % mod * Pow(sum[v], mod - 2) % mod) % mod;
		// 每个子节点集合内的数的贡献 = 数值 * 其他同级集合数量
	}

	sum[u] = (sum[u] + 1) % mod;	// 集合数加上自身单独成集
}

int main()
{
	memset(fr, -1, sizeof fr);

	int n, T;
	scanf("%d %d", &n, &T);

	for (int i = 1; i < n; i++)
	{
		int a, b;
		scanf("%d %d", &a, &b);
		addedge(a, b);
		addedge(b, a);
	}

	for (int i = 1; i <= n; i++)
	{
		if (T == 0)
			val[i] = 1;
		else
			val[i] = i;
	}	// 节点初始值

	dfs(1, 0);

	printf("%lld\n", val[1]);

	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值