对一个长度为 \(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; }