正题
题面链接:https://www.ybtoj.com.cn/contest/62/problem/3
题目大意
n n n个点的一棵树,每个边的边会表示一个大小关系(如 p x > p y p_x>p_y px>py或 p x < p y p_x<p_y px<py)。求有多少个排列满足所有条件。
解题思路
考虑树形 d p dp dp,设 f i , j f_{i,j} fi,j表示点 i i i的子树中有 j j j个比他小的数字时的方案数。
那么如果有条件
p
y
<
p
x
p_y<p_x
py<px考虑如何转移,我们枚举一下
i
,
j
i,j
i,j表示之前比
i
i
i小的数的个数和
y
y
y里面比
x
x
x小的个数,然后再枚举一个
k
k
k表示
y
y
y里面比
y
y
y小的数的个数。
然后用组合数插板表示方案,发现这样是
O
(
n
3
)
O(n^3)
O(n3)的,发现
k
k
k可以前/后缀和优化,所以可以缩掉,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
c o d e code code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5100,XJQ=998244353;
struct node{
ll to,next,w;
}a[N*2];
ll n,tot,ls[N],c[N][N],f[N][N],g[N],siz[N],ans;
ll C(ll n,ll m){return c[n+1][m+1];}
void addl(ll x,ll y,ll w){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;a[tot].w=w;
return;
}
void dfs(ll x,ll fa){
f[x][0]=1;siz[x]=1;
for(ll p=ls[x];p;p=a[p].next){
ll y=a[p].to;
if(y==fa)continue;
dfs(y,x);
for(ll i=0;i<=siz[x]+siz[y];i++)g[i]=0;
if(a[p].w){
for(ll i=0;i<siz[x];i++){
ll tmp=0;
for(ll j=siz[y]-1;j>=0;j--){
ll px=i,py=j;tmp=(tmp+f[y][j])%XJQ;
ll sx=siz[x]-i-1,sy=siz[y]-j-1;
(g[siz[x]+siz[y]-sx-sy-2]+=f[x][i]*tmp%XJQ*C(sx+sy+1,sx)%XJQ*C(px+py,py)%XJQ)%=XJQ;
}
}
}
else{
for(ll i=0;i<siz[x];i++){
ll tmp=0;
for(ll j=0;j<siz[y];j++){
ll px=i,py=j;tmp=(tmp+f[y][j])%XJQ;
ll sx=siz[x]-i-1,sy=siz[y]-j-1;
(g[px+py+1]+=f[x][i]*tmp%XJQ*C(px+py+1,px)%XJQ*C(sx+sy,sy)%XJQ)%=XJQ;
}
}
}
siz[x]+=siz[y];
for(ll i=0;i<=siz[x];i++)f[x][i]=g[i];
}
return;
}
int main()
{
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
scanf("%lld",&n);
for(ll i=1;i<n;i++){
ll x,y;scanf("%lld%lld",&x,&y);
addl(x,y,0);addl(y,x,1);
}
c[0][0]=1;
for(ll i=1;i<N;i++)
for(ll j=1;j<N;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%XJQ;
dfs(1,1);
for(ll i=0;i<siz[1];i++)
(ans+=f[1][i])%=XJQ;
printf("%lld\n",ans);
}