传送门
解析:
考场上没想到是组合数学,瞎打了一个O(n!)O(n!)O(n!)暴力滚粗了。
但是下来一看真的是组合数学sb题。。。
思路:
首先我们不要考虑标号的值域,我们只需要求出节点标号之间有多少可能的偏序关系,而不是求出实际的标号有多少种分配方式。
考虑我们现在求出了某个子树的标号偏序方案数。
那么我们需要合并uuu两个子树来更新现在的以uuu为根的子树的偏序方案数。
首先根据乘法原理,不考虑排列,他们分别的偏序方案数对总方案的贡献就是乘积。
然后考虑合并,设我们之前已经合并的子树总大小为nnn,现在需要新加入的子树大小为mmm
实际上就是在两个队列里面一直取队首的操作,实际贡献就是Cn+mmC_{n+m}^mCn+mm。
怎么理解呢。我们已经确定了子树内部的偏序关系,由于题目要求是严格偏序,所以可以用一个排好序的队列来存,而合并就是归并排序式的合并就行了。
考虑我们已经完成的最终长度为n+mn+mn+m的队列,而其中任取mmm个位置都可能放着原来队列中的元素,所以方案数就是组合数。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define int ll
inline int getint(){
re int num;
re char c;
while(!isdigit(c=gc()));num=c^48;
while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
return num;
}
cs ll mod=998244353;
cs int N=100005;
int last[N],nxt[N<<1],to[N<<1],ecnt;
inline void addedge(int u,int v){
nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}
int fac[N],inv[N],ifac[N];
inline int C(int n,int m){
return fac[n]*1ll*ifac[m]%mod*ifac[n-m]%mod;
}
ll g[N];
int siz[N];
inline void dfs(int u,int fa){
g[u]=1;
for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
if(v==fa)continue;
dfs(v,u);
siz[u]+=siz[v];
g[u]=g[u]*g[v]%mod;
g[u]=g[u]*C(siz[u],siz[v])%mod;
}++siz[u];
}
int n;
signed main(){
fac[0]=fac[1]=inv[0]=inv[1]=ifac[0]=ifac[1]=1;
for(int re i=2;i<N;++i){
fac[i]=1ll*fac[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
ifac[i]=ifac[i-1]*1ll*inv[i]%mod;
}
n=getint();
for(int re i=1;i<n;++i){
int u=getint(),v=getint();
addedge(u,v);
}
dfs(1,0);
cout<<g[1];
return 0;
}