[计数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;
}
posted @ 2026-01-12 20:14  Zwi  阅读(2)  评论(0)    收藏  举报