[BZOJ4011][HNOI2015]落忆枫音(拓扑排序+DP)

HNOI 2015 这是有多喜欢 dp ?六道题三道DP……
先考虑没有环的情况,设 di d i i i 的入度,那么构造一个以 1 为根的生成树,就相当于为除 1 1 之外的每个点选一个 father ,所以方案数为:

i=2ndi

把上式即为 dans d a n s
然后考虑在有环时,如何去除不合法(形成环)的方案数。
首先,如果 y=1 y = 1 ,那么所有的情况都合法。
否则 x x y 之间形成环的条件显然为:
存在一条从 y y x 的路径,并且 y y 的 father 为 x
而如果已经确定了这条路径,那么这种情况下不合法的方案数为:

i=2ndi[iyx] ∑ i = 2 n d i [ i 不 在 生 成 树 中 y 到 x 的 路 径 上 ]

考虑设 rp(T) r p ( T ) T T 是一条的路径):
rp(T)=i=2ndi[iT]

定义状态 f[u] f [ u ] 表示 Trp(T)[Tyu] ∑ T r p ( T ) [ T 是 一 条 从 y 到 u 的 路 径 ]
边界:
f[y]=dans×d1y f [ y ] = d a n s × d y − 1

利用拓扑排序进行转移,对于每一条边(不包括 <x,y> < x , y > <script type="math/tex" id="MathJax-Element-38"> </script>):
f[v]+=f[u]×d1v f [ v ] + = f [ u ] × d v − 1

1 − 1 为乘法逆元)
答案就比较显然了:
dansf[x] d a n s − f [ x ]

真不知道出题人把题意搞那么长有什么用意
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v; e; e = nxt[e])
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = 2e5 + 5, PYZ = 1e9 + 7;
int n, ecnt, d[N], nxt[M], adj[N], go[M], inv[N], cnt[N];
bool vis[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
int qpow(int a, int b) {
    int res = 1; while (b)
        b & 1 ? res = 1ll * res * a % PYZ : 0, a = 1ll * a * a % PYZ, b >>= 1;
    return res;
}
void dfs(int u) {
    vis[u] = 1; Edge(u) if (!vis[v = go[e]]) dfs(v);
}
int m, X, Y, sum = 1, H, T, que[N], f[N];
int main() {
    int i, x, y; n = read(); m = read(); X = read(); d[Y = read()]++;
    For (i, 1, m) x = read(), y = read(), add_edge(x, y), d[y]++;
    For (i, 2, n) sum = 1ll * sum * d[i] % PYZ; d[1] = 1;
    For (i, 1, n) inv[i] = qpow(d[i], PYZ - 2);
    H = 0; f[que[T = 1] = Y] = 1ll * sum * inv[Y] % PYZ;
    dfs(Y); For (i, 1, n) Edge(i)
        if (vis[i] && vis[v = go[e]]) cnt[v]++;
    while (H < T) {
        int u = que[++H]; Edge(u) {
            if (!vis[v = go[e]]) continue;
            if (!(--cnt[v])) que[++T] = v;
            f[v] = (f[v] + 1ll * f[u] * inv[v] % PYZ) % PYZ;
        }
    }
    if (Y == 1) cout << sum << endl;
    else cout << (sum - f[X] + PYZ) % PYZ << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值