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

本文解析了HNOI2015中的一道关于动态规划的题目,详细介绍了在有环和无环的情况下如何计算生成树的方案数。通过定义状态和使用拓扑排序进行转移,最终给出了代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

i=2ndi

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

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

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

定义状态 f[u]f[u] 表示 Trp(T)[Tyu]∑Trp(T)[T是一条从y到u的路径]
边界:
f[y]=dans×d1yf[y]=dans×dy−1

利用拓扑排序进行转移,对于每一条边(不包括 <x,y><x,y>):
f[v]+=f[u]×d1vf[v]+=f[u]×dv−1

1−1 为乘法逆元)
答案就比较显然了:
dansf[x]dans−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、付费专栏及课程。

余额充值