节日灯花(prufer序)

西南科技大学院赛 节日灯花 题解

题目

给定有标号有根树,求儿子节点数相同的树的个数

知识铺垫

prufer序列

  • 定义:一个带有标记的无根树,对应一个prufer序列
  • n个节点的树对应prufer序列长度为n - 2

树->prufer

  • 找到度为一的标号最小的节点,将它删除,并把它所连接的节点加入序列中
  • 直到剩余两个点停止
  • 一个点加入prufer时,说明它有一条连向叶子节点的边,加入后,那条边会删除,度数减一,若不存在连向其他叶子节点的边,那么该点就不会再次出现在prufer序列中,故点在prufer序列出现次数为度数-1

prufer->树

  • 维护一个未出现的集合S,从前遍往后遍历prufer序列,找到集合S中最小的未在prufer序列中出现过的元素,和prufer序列首项添加一条边,并删除两元素,重复n - 2次

应用

  • n n n个带标记的无根树方案为 n ( n − 2 ) n ^{(n - 2)} n(n2)
  • n n n个带标记的有根数方案为 n ( n − 1 ) n^{(n - 1)} n(n1)
  • n n n个节点,第 i i i个节点度为 d i d_i di,方案数为 ( n − 2 ) ! ∏ ( d i − 1 ) ! \frac{(n-2)!}{\prod({d_i-1})!} (di1)!(n2)!

题解:

  • 首先我们知道每个节点的度为 d i d_i di
  • 可以推出根节点儿子节点个数 s o n i = d i son_i =d_i soni=di,非根节点 s o n i = d i − 1 son_i=d_i-1 soni=di1
  • 因为可以通过排序,那么只要满足排完序列 s o n i son_i soni相等即可
  • 统计 c n t i cnt_i cnti s o n i son_i soni的个数
  • 根节点的选法有 s u m i = ∑ c n t i ∗ s o n i sum_i =\sum{cnt_i}*son_i sumi=cntisoni
  • 选完根节点后,还有 n − 1 n-1 n1个点,剩余点选剩余的 s o n i son_i soni根据多重排列可得方案数为 ( n − 1 ) ! ∏ c n t i \frac{(n-1)!}{\prod{cnti}} cnti(n1)!
  • 故最后总方案数为 s u m i ∗ ( n − 2 ) ! ∏ ( s o n i ) ! ∗ ( n − 1 ) ! ∏ c n t i sum_i*\frac{(n-2)!}{\prod({son_i})!}*\frac{(n-1)!}{\prod{cnti}} sumi(soni)!(n2)!cnti(n1)!

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for (int i = a; i <= (int)(b); i ++ )
#define x first
#define y second
 
using namespace std;
using ll = long long;

const int maxn = 1e5 + 10, mod = 1e9 + 7;
ll din[maxn], cnt[maxn];
ll fac[maxn], invfac[maxn], inv[maxn];

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

void get()
{
	fac[0] = 1;
	invfac[0] = 1;
	rep(i,1,maxn-1) fac[i] = fac[i-1] * i % mod;
	rep(i,1,maxn-1) invfac[i] = qmi(fac[i], mod-2);
}
typedef pair<int,int> pii;
pii a[maxn];

void init()
{
	memset(din,0,sizeof din);
	memset(cnt,0,sizeof cnt);
	memset(a,0,sizeof a); 
}


void solve()
{
	init();
	int n;
	std::cin >> n;
	rep(i,1,n-1)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		din[u]++,din[v]++;			
	}
	rep(i,2,n) din[i] --;
	rep(i,1,n)
	{
		cnt[din[i]]++;
	}
	ll idx = 0;
	rep(i,0,n)
	{
		if(cnt[i]) a[++idx] = make_pair(i, cnt[i]);
	}
	ll ans = fac[n-1] * fac[n-2] % mod;
	ll sum = 0;
 	rep(i,1,idx)
 	{
 		sum = (sum + 1ll * a[i].x * a[i].y % mod) % mod;
 		ans = (ans * invfac[a[i].y]) % mod;	
 		rep(j,1,a[i].y) ans = (ans * invfac[a[i].x]) % mod;
	}
	std::cout << ans * sum % mod << "\n";
}  
int main()
{
	get();
	#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
	#endif
	int t;
	std::cin >> t;
	while(t -- )
	{
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值