[BZOJ 3167][HEOI 2013]SAO
题意
对一个长度为 (n) 的排列作出 (n-1) 种限制, 每种限制形如 "(x) 在 (y) 之前" 或 "(x) 在 (y) 之后". 且保证任意两点之间都有直接或间接的限制关系. 求方案数量.
(nle 1000).
题解
Sword Art Online还行
拖了很久终于想起这题了...
首先我们发现这个限制关系是树状的, 那么我们尝试用子树来定义状态. 设 (dp_{i,j}) 表示以 (i) 为根的子树中的元素组成的合法排列且 (i) 在升序下排在第 (j) 名的方案数量. 那么我们要做的就是将两个排列在符合限制的条件下保序合并.
保序合并比较简单, 枚举一个排列在合并后的排列中占据哪些位置就可以了. 显然这是一个简单的组合数.
为了符合限制条件, 我们需要把在最终排列中排在当前根之前的结点和之后的结点区别对待. 枚举子树中有 (j) 个点排在根之前. 然后将限制分两类讨论:
-
若限制是子结点先于根节点, 那么设根节点为 (r), 子结点为 (s), 有:
[dp_{r,i+j}={i+j-1choose i-1}{ ext{size}_r+ ext{size}_s-i-jchoose ext{size}_s-j}dp_{r,i}sum_{k=1}^jdp_{s,k} ] -
若限制是根结点先于子结点, 那么有:
[dp_{r,i+j}={i+j-1choose i-1}{ ext{size}_r+ ext{size}_s-i-jchoose ext{size}_s-j}dp_{r,i}sum_{k=j+1}^{ ext{size}_s}dp_{s,k} ]
其中等式右侧(包括 ( ext{size}_r) )都是合并 (s) 所在子树信息前的信息.
不难发现这个东西是个 (O(n^3)) 的爆炸复杂度. 但是转移式最后的和式是个前/后缀和的形式, 预处理一下就可以均摊 (O(1)) 得到那个和式的值了.
于是复杂度就变成 (O(n^2)) 了.
以及这题复杂度的分析, 我们直接看转移式会感觉它单次转移是 (O(n^2)) 的, 实际上 (i) 和 (j) 的枚举范围分别只有 ( ext{size}_r) 和 ( ext{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
",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;
}