洛谷 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\) 内的边是父亲指向孩子,其他边无所谓,也就是没有定向的功能。此时由容斥关系有:
然后发现 \(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;
}

浙公网安备 33010602011771号