對一個長度爲 \(n\) 的排列做出 \(n-1\) 種限制, 每種限制形如 "\(x\) 在 \(y\) 以前" 或 "\(x\) 在 \(y\) 以後". 且保證任意兩點之間都有直接或間接的限制關係. 求方案數量.php
\(n\le 1000\).c++
Sword Art Online還行spa
拖了好久終於想起這題了...code
首先咱們發現這個限制關係是樹狀的, 那麼咱們嘗試用子樹來定義狀態. 設 \(dp_{i,j}\) 表示以 \(i\) 爲根的子樹中的元素組成的合法排列且 \(i\) 在升序下排在第 \(j\) 名的方案數量. 那麼咱們要作的就是將兩個排列在符合限制的條件下保序合併.blog
保序合併比較簡單, 枚舉一個排列在合併後的排列中佔據哪些位置就能夠了. 顯然這是一個簡單的組合數.get
爲了符合限制條件, 咱們須要把在最終排列中排在當前根以前的結點和以後的結點區別對待. 枚舉子樹中有 \(j\) 個點排在根以前. 而後將限制分兩類討論:it
若限制是子結點先於根節點, 那麼設根節點爲 \(r\), 子結點爲 \(s\), 有:
\[ dp_{r,i+j}={i+j-1\choose i-1}{\text{size}_r+\text{size}_s-i-j\choose \text{size}_s-j}dp_{r,i}\sum_{k=1}^jdp_{s,k} \]class
若限制是根結點先於子結點, 那麼有:
\[ dp_{r,i+j}={i+j-1\choose i-1}{\text{size}_r+\text{size}_s-i-j\choose \text{size}_s-j}dp_{r,i}\sum_{k=j+1}^{\text{size}_s}dp_{s,k} \]im
其中等式右側(包括 \(\text{size}_r\) )都是合併 \(s\) 所在子樹信息前的信息.next
不難發現這個東西是個 \(O(n^3)\) 的爆炸複雜度. 可是轉移式最後的和式是個前/後綴和的形式, 預處理一下就能夠均攤 \(O(1)\) 獲得那個和式的值了.
因而複雜度就變成 \(O(n^2)\) 了.
以及這題複雜度的分析, 咱們直接看轉移式會感受它單次轉移是 \(O(n^2)\) 的, 實際上 \(i\) 和 \(j\) 的枚舉範圍分別只有 \(\text{size}_r\) 和 \(\text{size}_s\). 總枚舉量就是 "當前要合併的子樹大小" 與 "已經合併過的兄弟子樹大小之和" 的積. 那麼對於 \(r\) 的轉移枚舉量其實就等於以 \(r\) 爲LCA的點對數量. 那麼整棵樹的總枚舉量就是 \(O(n^2)\) 了.
然而排列是 \([0,n)\) 的...
#include <bits/stdc++.h> const int MAXN=1010; const int MOD=1e9+7; struct Edge{ int from; int to; int typ; Edge* next; }; Edge E[MAXN*2]; Edge* head[MAXN]; Edge* top=E; int n; int tmp[MAXN]; int inv[MAXN]; int fact[MAXN]; int size[MAXN]; int pf[MAXN][MAXN]; int sf[MAXN][MAXN]; int dp[MAXN][MAXN]; int C(int,int); void DFS(int,int); int Pow(int,int,int); void Insert(int,int,int); int main(){ int T; scanf("%d",&T); while(T--){ memset(dp,0,sizeof(dp)); memset(pf,0,sizeof(pf)); memset(sf,0,sizeof(sf)); memset(head,0,sizeof(head)); top=E; scanf("%d",&n); fact[0]=1; for(int i=1;i<=n;i++) fact[i]=1ll*fact[i-1]*i%MOD; inv[n]=Pow(fact[n],MOD-2,MOD); for(int i=n;i>=1;i--) inv[i-1]=1ll*inv[i]*i%MOD; for(int i=1;i<n;i++){ int a,b; char op; scanf("%d %c%d",&a,&op,&b); Insert(a,b,op=='>'); Insert(b,a,op=='<'); } DFS(0,-1); printf("%d\n",sf[0][1]); } return 0; } void DFS(int root,int prt){ size[root]=1; dp[root][1]=1; for(Edge* k=head[root];k!=NULL;k=k->next){ if(k->to!=prt){ DFS(k->to,root); memset(tmp,0,sizeof(tmp)); if(k->typ){ for(int i=1;i<=size[root];i++){ for(int j=1;j<=size[k->to];j++){ tmp[i+j]=(tmp[i+j]+1ll*C(i+j-1,i-1)*C(size[root]+size[k->to]-i-j,size[k->to]-j)%MOD*dp[root][i]%MOD*pf[k->to][j])%MOD; } } } else{ for(int i=1;i<=size[root];i++){ for(int j=0;j<size[k->to];j++){ tmp[i+j]=(tmp[i+j]+1ll*C(i+j-1,i-1)*C(size[root]+size[k->to]-i-j,size[k->to]-j)%MOD*dp[root][i]%MOD*sf[k->to][j+1])%MOD; } } } size[root]+=size[k->to]; memcpy(dp[root],tmp,sizeof(tmp)); } } for(int i=1;i<=size[root];i++) pf[root][i]=(dp[root][i]+pf[root][i-1])%MOD; for(int i=size[root];i>=1;i--) sf[root][i]=(dp[root][i]+sf[root][i+1])%MOD; } inline void Insert(int from,int to,int typ){ top->from=from; top->to=to; top->typ=typ; top->next=head[from]; head[from]=top++; } inline int C(int n,int m){ return n<0||m<0||n<m?0:1ll*fact[n]*inv[m]%MOD*inv[n-m]%MOD; } inline int Pow(int a,int n,int p){ int ans=1; while(n>0){ if(n&1) ans=1ll*a*ans%p; a=1ll*a*a%p; n>>=1; } return ans; }