【题解】[HEOI2013]SAO

发布时间 2023-03-30 09:17:29作者: linyihdfj

题目分析:

考虑这是一个树形图,所以就先直接当作树来做。
这个题其实就是让我们求解有多少种拓扑序而且题目中边方向的限制其实就是在限制拓扑序的前后,而一般这种题在设计 \(dp\) 状态时都会考虑将拓扑序放到状态里,因为如果不这样干拓扑序就很难限制。
也就是设 \(dp[i][j]\) 表示以 \(i\) 为根的子树,\(i\) 在拓扑序中排第 \(j\) 位的方案数,那么转移就显然是合并子树,也就是:

\[dp[u][j] \times dp[v][k] \times C \to dp[u][i] \]

这个转移显然需要乘以一个 \(C\),那么下面就是找这个 \(C\) 到底是什么。
考虑这个合并本质上就是两个序列的合并,因为 \(u\) 子树内访问过的点和 \(v\) 子树的拓扑序分别构成两个序列,现在就是要糅合到一起。
其实也是非常简单的,就是考虑 \(u\) 前面放多少个以及 \(u\) 后面放多少个就好了,即:

\[C = \binom{i-1}{i-j} \times \binom{sz_u + sz_v - i}{sz_v - (i - j)} \]

这样就可以实现 \(O(n^3)\) 的转移。
但是我们也可以发现一点,在转移方程中 \(dp[v][k]\) 中所要枚举的 \(k\) 与其他的项毫无关系,所以就可以考虑直接做一个前缀和,这样就可以不用枚举 \(k\) 了。

上文这种 \(dp\) 的主要原因就是为了方便我们加入边方向的限制,其实边方向的限制就是限制 \(u,v\) 在拓扑序中谁在前谁在后。
\(u\)\(v\) 之前:\(i-j < k \to i - j \le k - 1 \to i - j + 1 \le k\)
\(u\)\(v\) 之后:\(i-j \ge k\)
因为我们要在 \(u\) 之前插入 \(i-j\) 个来自 \(v\) 子树中的数,而 \(v\) 排在第 \(k\) 个,所以限制一下 \(i-j\)\(k\) 的大小关系就知道 \(v\) 有没有被插入到 \(u\) 之前。
其实就是限制一下 \(k\) 就可以限制拓扑序的前后,就很简单了。
我们就不妨指定 \(1\) 为根,然后去转移,答案即 \(\sum_{i=1}^n dp[1][i]\)

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2005;
const int MOD = 1e9+7;
struct edge{
	int nxt,to;
	edge(){}
	edge(int _nxt,int _to){
		nxt = _nxt,to = _to;
	}
}e[2 * N];
int head1[N],head2[N],C[N][N],cnt,f[N][N],sz[N],g[N];
void add_edge(int *head,int from,int to){
	e[++cnt] = edge(head[from],to);
	head[from] = cnt;
}
int binom(int n,int m){
	if(n < 0 || m < 0 || n < m)	return 0;
	return C[n][m];	
}
void dfs(int now,int fath){
	memset(f[now]+1,0,sizeof(int)*sz[now]);
	sz[now] = 1,f[now][1] = 1;
	for(int i=head1[now]; i;i=e[i].nxt){  //要求 now 在 to 之前 
		int to = e[i].to;
		if(to == fath)	continue;
		dfs(to,now);memcpy(g+1,f[now]+1,sizeof(int)*sz[now]);memset(f[now]+1,0,sizeof(int)*sz[now]);
		for(int j=1; j<=sz[now]; j++){
			for(int i=j; i<=sz[to]+j-1; i++){
				f[now][i] = (f[now][i] + g[j]*binom(i-1,i-j)%MOD*binom(sz[now]+sz[to]-i,sz[to]-(i-j))%MOD*(f[to][sz[to]]-f[to][i - j] + MOD)%MOD)%MOD;
			}
		}
		sz[now] += sz[to];
	}
	for(int i=head2[now]; i;i=e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dfs(to,now);memcpy(g+1,f[now]+1,sizeof(int)*sz[now]);memset(f[now]+1,0,sizeof(int)*sz[now]);
		for(int j=1; j<=sz[now]; j++){
			for(int i=j+1; i<=sz[to]+j; i++){
				f[now][i] = (f[now][i] + g[j]*binom(i-1,i-j)%MOD*binom(sz[now]+sz[to]-i,sz[to]-(i-j))%MOD*f[to][i-j]%MOD)%MOD;
			}
		}
		sz[now] += sz[to];
	}
//	for(int i=1; i<=sz[now]; i++)	printf("f[%lld][%lld] = %lld\n",now,i,f[now][i]);
	for(int i=1; i<=sz[now]; i++)	f[now][i] = (f[now][i] + f[now][i-1])%MOD;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int n;scanf("%lld",&n);
		for(int i=1; i<n; i++){
			int a,b;string s;
			cin>>a>>s>>b;a++,b++;
			if(s == "<")	add_edge(head1,a,b),add_edge(head2,b,a);
			else	add_edge(head1,b,a),add_edge(head2,a,b);
		}
		C[0][0] = 1;
		for(int i=1; i<=n; i++){
			C[i][0] = 1;
			for(int j=1; j<=n; j++){
				C[i][j] = (C[i-1][j] + C[i-1][j-1])%MOD;
			}
		}
		dfs(1,0);
		int ans = f[1][sz[1]];
		printf("%lld\n",ans);
		for(int i=1; i<=n; i++)	head1[i] = head2[i] = 0;
		cnt = 0;
	}
	return 0;
}