题解:[集训队互测 2023] Tree Topological Order Counting

题目传送门

定义 \(f(x)\) 为这棵树的所有合法拓扑序 \(a\)\(b_{a_x}\) 之和,求 \(f(1),f(2),\cdots,f(n)\bmod10^9+7\)

题意分析

显然考虑 DP 计数。

对于树上拓扑序计数,一个经典结论是拓扑序的方案数为 \(\dfrac{n!}{\displaystyle\prod_{i=1}^n\textit{size}_i}\)

将长度为 \(m\) 的有序序列插入长度为 \(n\) 的有序序列,共有 \(\dbinom{n+m}m\) 种方案。

考虑设计 DP 状态,直接设计 \(f_{x,i}\)\(x\) 的拓扑序为 \(i\),这样不好转移,因为无法保证拓扑序为一个排列。对于这种要求是排列的问题,也许可以在状态里加入排名,合并就考虑插入进去。

\(f_{x,i}\) 表示不考虑子树 \(x\) 内部,\(x\) 的拓扑序排名为 \(i\) 的方案数。

因为这个东西是子树内的拓扑序,所以考虑合并子树不好做,从上往下转移。

初始时显然有 \(f_{1,1}=1\)

\(x\) 的状态 \(f_{x,i}\) 转移到 \(v\) 的子节点 \(f_{v,j}\),实际上是一个剥离掉 \(v\) 的兄弟结点的过程,考虑 \(f_{x,i}\) 对于 \(f_{v,j}\) 的系数是什么。

  • 首先,\(x\) 的子树里除了 \(x\) 的所有节点的拓扑序显然要大于 \(i\),所以合并上去之后是要在 \(i\) 后面的,等价于确定了前 \(i\) 个。

    考虑插入进去,要把 \(v\) 的兄弟子树共 \(\textit{size}_x-\textit{size}_v-1\) 节点插入到原本的 \(n-\textit{size}_x+1-i\) 个拓扑序进去,方案数为:

    \[\dbinom{n-\textit{size}_x+1-i+\textit{size}_x-\textit{size}_v-1}{\textit{size}_x-\textit{size}_v-1}=\dbinom{n-\textit{size}_v-i}{\textit{size}_x-\textit{size}_v-1} \]

  • 其次,需要乘上 \(v\) 的兄弟子树的拓扑序计数方案数:

    \[\dfrac{(\textit{size}_x-\textit{size}_v-1)!}{\displaystyle\prod_{u\in\operatorname{subtree}(x)\setminus\operatorname{subtree}(v)\\u\neq v}\textit{size}_u} \]

整理一下,有:

\[f_{v,j}=\sum_{i=1}^{j-1}f_{x,i}\dbinom{n-\textit{size}_v-i}{\textit{size}_x-\textit{size}_v-1}\dfrac{(\textit{size}_x-\textit{size}_v-1)!}{\displaystyle\prod_{u\in\operatorname{subtree}(x)\setminus\operatorname{subtree}(v)\\u\neq v}\textit{size}_u} \]

然后考虑如何对 \(f_{x,i}\) 统计答案 \(\textit{ans}_x\)

考虑把子树内的 \(\textit{size}_x-1\) 个拓扑序插入到全局 \(n-\textit{size}_x+1-i\) 个,然后再对子树 \(x\) 做拓扑序计数,有:

\[\textit{ans}_x=\sum_{i=1}^nf_{x,i}\cdot b_i\dbinom{n-i}{\textit{size}_x-1}\dfrac{\textit{size}_x!}{\displaystyle\prod_{u\in\operatorname{subtree}(x)}\textit{size}_u} \]

注意到这里因为子树 \(x\) 全部在 \(x\) 的拓扑序后面,因此可以直接认为拓扑序为 \(i\)。前面不行使因为涉及到转移。

实际实现上可以维护 \(\displaystyle h_x=\prod_{u\in\operatorname{subtree}(x)}\textit{size}_u\) 及其逆元,再做一遍 \(f_{x,i}\dbinom{n-\textit{size}_v-i}{\textit{size}_x-\textit{size}_v-1}\) 的前缀和即可 \(\mathcal O(n^2)\) 维护。

AC 代码

//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=5000,V=N,P=1e9+7;
int n,b[N+1],father[N+1],size[N+1],f[N+1][N+1],h[N+1],ans[N+1];
int invH[N+1];
vector<int>g[N+1];
int fact[V+1],invFact[V+1];
int qpow(int base,int n){
	int ans=1;
	while(n){
		if(n&1){
			ans=1ll*ans*base%P;
		}
		base=1ll*base*base%P;
		n>>=1;
	}
	return ans;
}
void pre(){
	fact[0]=1;
	for(int i=1;i<=V;i++){
		fact[i]=1ll*fact[i-1]*i%P;
	}
	invFact[V]=qpow(fact[V],P-2);
	for(int i=V-1;0<=i;i--){
		invFact[i]=invFact[i+1]*(i+1ll)%P;
	}
}
int C(int n,int m){
	if(n<0||m<0||n<m){
		return 0;
	}
	return 1ll*fact[n]*invFact[m]%P*invFact[n-m]%P;
}
void dfs1(int x){
	size[x]=1;
	h[x]=1;
	for(int v:g[x]){
		dfs1(v);
		size[x]+=size[v];
		h[x]=1ll*h[x]*h[v]%P;
	}
	h[x]=1ll*h[x]*size[x]%P;
	invH[x]=qpow(h[x],P-2);
}
void dfs2(int x){
	for(int i=1;i<=n;i++){
		ans[x]=(ans[x]+1ll*f[x][i]*b[i]%P*C(n-i,size[x]-1)%P*fact[size[x]]%P*invH[x]%P)%P;
	}
	for(int v:g[x]){
		static int tmp[N+1];
		for(int i=1;i<=n;i++){
			tmp[i]=(tmp[i-1]+1ll*C(n-size[v]-i,size[x]-size[v]-1)*f[x][i])%P;
		}
		for(int j=1;j<=n;j++){
			f[v][j]=tmp[j-1];
		}
		for(int j=1;j<=n;j++){
			f[v][j]=1ll*f[v][j]*fact[size[x]-size[v]-1]%P*invH[x]%P*size[x]%P*h[v]%P;
		}
		dfs2(v);
	}
} 
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	pre();
	cin>>n;
	for(int i=2;i<=n;i++){
		cin>>father[i];
		g[father[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	f[1][1]=1;
	dfs1(1);
	dfs2(1);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	cout<<'\n';
	
	cout.flush(); 
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-08-23 17:52  TH911  阅读(4)  评论(0)    收藏  举报