题解:[集训队互测 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_{x,i}\) 统计答案 \(\textit{ans}_x\)。
考虑把子树内的 \(\textit{size}_x-1\) 个拓扑序插入到全局 \(n-\textit{size}_x+1-i\) 个,然后再对子树 \(x\) 做拓扑序计数,有:
注意到这里因为子树 \(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;
}

浙公网安备 33010602011771号