【YbtOJ#20081】樹上排列
阿新 • • 發佈:2020-11-24
題目
題目連結:https://www.ybtoj.com.cn/contest/62/problem/3
思路
可以看做有多少個 \(1\sim n\) 的排列滿足對於一條路徑 \(u\to v\),\(u\) 在序列中的位置一定在 \(v\) 在序列中的位置的前面。
設 \(f[x][i]\) 表示以 \(x\) 為根的子樹內,\(x\) 在序列中是第 \(i\) 個數的方案數。
考慮加入 \(x\) 的一個子樹 \(y\),列舉在子樹 \(y\) 內有多少個數字排在 \(x\) 前面,那麼最終合併之後點 \(x\) 是排在位置 \(i+j\) 的。
而前面的 \((i-1)+j\) 個數字可以任意排列,後面同理,所以方案數為
其中 \(s\) 是 \(y\) 內的方案數,具體的,當 \(p_x<p_y\) 時,\(s=\Pi^{\mathrm{size}(y)}_{k=j+1}f[y][k]\),否則 \(s=\Pi^{j}_{k=1}f[y][k]\)。
由於我們只會列舉到 \(\mathrm{size}(y)\),所以兩個點只會在他們 LCA 處計算,時間複雜度 \(O(n^2)\)
程式碼
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll N=3010,MOD=998244353; ll n,tot,f[N][N],g[N],head[N],size[N],C[N][N]; ll ans; struct edge { ll next,to,id; }e[N*2]; void add(ll from,ll to,ll id) { e[++tot].to=to; e[tot].id=id; e[tot].next=head[from]; head[from]=tot; } void dfs(ll x,ll fa) { size[x]=1; f[x][1]=1; for (ll i=head[x];~i;i=e[i].next) { ll v=e[i].to; if (v!=fa) { dfs(v,x); size[x]+=size[v]; for (ll j=0;j<=size[x];j++) g[j]=f[x][j],f[x][j]=0; ll sum=0; if (e[i].id) for (ll j=1;j<=size[x]-size[v];j++,sum=0) for (ll k=size[v];k>=0;k--) { f[x][j+k]=(f[x][j+k]+1LL*C[j+k-1][j-1]*C[size[x]-j-k][size[v]-k]%MOD*g[j]%MOD*sum)%MOD; sum=(sum+f[v][k])%MOD; } else for (ll j=1;j<=size[x]-size[v];j++,sum=0) for (ll k=0;k<=size[v];k++) { sum=(sum+f[v][k])%MOD; f[x][j+k]=(f[x][j+k]+1LL*C[j+k-1][j-1]*C[size[x]-j-k][size[v]-k]%MOD*g[j]%MOD*sum)%MOD; } } } } int main() { freopen("perm.in","r",stdin); freopen("perm.out","w",stdout); memset(head,-1,sizeof(head)); C[0][0]=C[1][0]=C[1][1]=1; for (ll i=2;i<=3000;i++) { C[i][0]=1; for (ll j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD; } scanf("%lld",&n); for (ll i=1,x,y;i<n;i++) { scanf("%lld%lld",&x,&y); add(x,y,1); add(y,x,0); } dfs(1,0); for (ll i=1;i<=n;i++) ans=(ans+f[1][i])%MOD; printf("%lld",ans); return 0; }