洛谷 P4099 [HEOI2013] SAO

传送门

题意:给定一个 \(n\) 个点的有向图,保证将有向边视为无向边后,图构成一棵树。求其拓扑序个数,\(n\le 1000\),对 \(10^9+7\) 取模。

先考虑树是以 \(1\) 为根的外向树怎么做。有一个 DP:设 \(f_u\) 表示 \(u\) 为根的子树内的拓扑序个数。对于子树,\(u\) 自身一定要第一个选。剩下来子树先钦定一种组合顺序,也即一个广义组合数,再乘上各自方案。故 \(f_u \gets \binom{sz_u-1}{sz_{v_1}, sz_{v_2}, \dots, sz_{v_k}}\times\prod_{v} f_v\),其中 \(sz\) 表示子树大小。不难看出,答案 \(f_1=\frac{n!}{\prod_{i=1}^n sz_i}\)

同理,对于一个外向森林,定根后答案仍然为 \(\frac{n!}{\prod_{i=1}^n sz_i}\)

不妨设树以 \(1\) 为根。称儿子指向父亲的边为反向边。可以考虑容斥掉这些反向边的限制。这里设 \(F_S\) 表示恰好 \(S\) 内的边是父亲指向孩子,剩下的边是孩子指向父亲的方案数。\(G_S\) 表示至少 \(S\) 内的边是父亲指向孩子,其他边无所谓,也就是没有定向的功能。此时由容斥关系有:

\[F_S = \sum_{S \subseteq T} (-1)^{|T|-|S|}G_T \]

然后发现 \(G_S\) 的限制就是一个外向森林的限制。
这些无所谓的边,将树分成了若干个连通块,然后无所谓的边的个数恰好就是 \(|T|-|S|\)

我们带着容斥系数进行 DP 的时候,考虑设 \(f_{u,i}\) 表示,\(u\) 为根的子树内,当前点所包含的连通块大小为 \(i\),的方案数。

把上面的式子搬到下面,\(n!\) 是常数可以提出,剩下的就是对于 \(\frac 1{\prod sz_i}\) 的 DP。
然后转移是一个树上背包,转移的时候乘上 \(-1\) 的容斥系数就好了。具体地,就是看看一条边会不会被钦定为【无所谓】。当然只有反向边才可能会被钦定成【无所谓】。

总时间复杂度 \(O(n^2)\)

#include <bits/stdc++.h>
using namespace std;

//#define filename "xxx" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
#define multi_cases 1

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 1002;

const int P = 1000000007;
void vadd(int &a, int b) { a += b; if(a >= P) a -= P; }

int inv[N];

int n;
vector<pii> G[N];

int sz[N], f[N][N], coe;
void DFS(int u, int fa) {
	sz[u] = 1;
	f[u][1] = 1;
	for(auto [v, w] : G[u]) if(v != fa) {
		DFS(v, u);
		vector<int> t(sz[u] + sz[v] + 1);
		upw(i, 1, sz[u]) upw(j, 1, sz[v]) vadd(t[i + j], 1ll * f[u][i] * f[v][j] % P);
		if(w) {
			coe = P - coe;
			upw(i, 1, sz[u]) upw(j, 1, sz[v]) vadd(t[i], 1ll * f[u][i] * f[v][j] % P * (P-1) % P);
		}
		sz[u] += sz[v];
		upw(i, 1, sz[u]) f[u][i] = t[i];
	}
	upw(i, 1, sz[u]) f[u][i] = 1ll * f[u][i] * inv[i] % P;
}

void WaterM() {
	cin >> n;
	upw(i, 1, n-1) {
		int u, v;
		char c;
		cin >> u >> c >> v, ++u, ++v;
		G[u].emplace_back(v, c != '<'), G[v].emplace_back(u, c != '>');
	}
	coe = 1;
	DFS(1, 0);
	
	int ans = 0;
	upw(i, 1, n) vadd(ans, f[1][i]);
	upw(i, 1, n) ans = 1ll * ans * i % P;
	cout << 1ll * ans * coe % P << '\n';
	
	upw(i, 1, n) clear(G[i]);
	upw(i, 1, n) upw(j, 1, n) f[i][j] = 0;
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	
	inv[1] = 1;
	upw(i, 2, 1000) inv[i] = 1ll * (P - P / i) * inv[P % i] % P;
	
	while(_--) WaterM();
	return 0;
}
posted @ 2025-09-04 22:54  Water_M  阅读(8)  评论(0)    收藏  举报