[计数dp] [拓扑排序] P3244 [HNOI2015] 落忆枫音
posted on 2024-06-07 06:02:07 | under | source
记一个点入度 \(i\) 为 \(ru_i\)。
考虑没有边 \(x\to y\) 怎么做,直接拓扑计数是不可行的,可以用父亲表示法,为每个点选择父亲,可以证明,在 \(\rm DAG\) 中瞎选父亲一定构成一棵树(考虑树的定义)。
假如加上边 \(x\to y\),按上面的方法算出现在的总方案是 \(\prod\limits_{i\ne 1} ru_i\),减去生成的非法方案即可。
非法情况就是树上出现了环,假如知道这个环的点集 \(P\),那么方案数就是固定这个环然后为其它点钦定,即 \(\prod\limits_{i\notin P} ru_i\)。
转化一下是 \(\frac{S}{\prod\limits_{i\in P} ru_i}\),可以直接拓扑序 \(\rm dp\),初始化 \(f_y=\frac S{ru_y}\),非法方案是 \(f_x\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 1e9 + 7;
int n, m, u, v, x, y, tp_ru[N], ru[N], inv_ru[N], S, f[N];
vector<int> to[N];
inline void add(int u, int v) {to[u].push_back(v), ++ru[v];}
inline int qstp(int a, int k) {int res = 1; for(; k; a = a * a % mod, k >>= 1) if(k & 1) res = res * a % mod; return res;}
inline void topo(){
queue<int> q;
q.push(1), f[y] = S * inv_ru[y] % mod;
while(!q.empty()){
int u = q.front(); q.pop();
for(auto v : to[u]){
f[v] = (f[v] + f[u] * inv_ru[v] % mod) % mod;
if(!(--tp_ru[v])) q.push(v);
}
}
}
signed main(){
cin >> n >> m >> x >> y, S = 1;
for(int i = 1; i <= m; ++i) scanf("%lld%lld", &u, &v), add(u, v);
memcpy(tp_ru, ru, sizeof tp_ru);
++ru[y];
for(int i = 2; i <= n; ++i) S = S * ru[i] % mod, inv_ru[i] = qstp(ru[i], mod - 2);
topo();
cout << (S - f[x] + mod) % mod;
return 0;
}

浙公网安备 33010602011771号