Luogu P13020 [GESP202506 八级] 遍历计数 题解 [ 绿 ] [ 组合数学 ] [ 前缀和 ] [ 树形 DP ]

遍历计数:还以为 GESP 也出了个树的遍历,结果是个【数据删除】题,鉴定为打 NOIP2024 打的。

先考虑钦定某个根的情况,此时不难想出一个树形 DP\(dp_i\) 表示 \(i\) 的子树内遍历的情况数,转移方程为:

  • \(i \ne root , dp_{i}=(d_i-1)!\times \prod_{j} dp_j\)
  • \(i=root,dp_{i}=d_i!\times \prod_{j} dp_j\)

其中 \(d_i\) 表示 \(i\) 的度数。可以理解为随意安排走子节点的顺序。

不断往根节点的 DP 值代入,不难发现 \(dp_{root}\) 其实就是 \(d_{root}!\times \prod_{j\ne root}(d_j-1)!\)

这个式子是很好计算的,于是我们先预处理阶乘,然后枚举根节点,算出其余节点的贡献乘积即可。具体地,把所有点拍到一个序列上,预处理前缀和后缀的乘积,即可做到 \(O(n)\) 求每个节点为根时的答案。

其实这个题就是树的遍历里 \(k=1\) 的部分分加强了一下,但我当时愣是没想出来这 24pts,我才是奶龙!!!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
const int N=100005;
const ll mod=1e9;
ll n,d[N],pre[N],suf[N],g[N],ans;
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		d[u]++;d[v]++;
	}
	g[0]=1;
	for(int i=1;i<=n;i++)
		g[i]=(g[i-1]*i)%mod;
	pre[0]=suf[n+1]=1;
	for(int i=1;i<=n;i++)
		pre[i]=(pre[i-1]*g[d[i]-1])%mod;
	for(int i=n;i>=1;i--)
		suf[i]=(suf[i+1]*g[d[i]-1])%mod;
	for(int i=1;i<=n;i++)
		ans=(ans+pre[i-1]*suf[i+1]%mod*g[d[i]]%mod)%mod;
	cout<<ans;
	return 0;
}
posted @ 2025-06-30 15:31  KS_Fszha  阅读(184)  评论(0)    收藏  举报