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;
}

浙公网安备 33010602011771号