正题
题面链接:https://www.ybtoj.com.cn/contest/62/problem/3
题目大意
nnn个点的一棵树,每个边的边会表示一个大小关系(如px>pyp_x>p_ypx>py或px<pyp_x<p_ypx<py)。求有多少个排列满足所有条件。
解题思路
考虑树形dpdpdp,设fi,jf_{i,j}fi,j表示点iii的子树中有jjj个比他小的数字时的方案数。
那么如果有条件py<pxp_y<p_xpy<px考虑如何转移,我们枚举一下i,ji,ji,j表示之前比iii小的数的个数和yyy里面比xxx小的个数,然后再枚举一个kkk表示yyy里面比yyy小的数的个数。
然后用组合数插板表示方案,发现这样是O(n3)O(n^3)O(n3)的,发现kkk可以前/后缀和优化,所以可以缩掉,时间复杂度为O(n2)O(n^2)O(n2)
codecodecode
#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);
}