[BZOJ4013][HNOI2015]实验比较(树形DP+组合数学)

先用并查集将所有用等号连接的点缩成一个。然后看到题目中有一个很重要的条件:
对每张图片 i i ,小D都最多只记住了某一张质量不比i差的另一张图片 Ki K i
缩点后建树,对于每个 i i ,如果Ki存在,就将 Ki K i 作为 i i 的父节点。建树后有可能是一棵森林,所以新建一个新的节点n+1连接森林里每棵树的根节点,形成一棵树, n+1 n + 1 为根。
然后树形DP, f[u] f [ u ] 表示 u u 的子树内的方案数。
但对于u的两个不同子节点 v v w v v w的子树内可能存在两个点质量相等,所以还需要加一维:
f[u][i] f [ u ] [ i ] 表示 u u 的子树里,分成i段(也就是共有 i1 i − 1 个小于号把质量序列分成了 i i 个部分,每个部分里的图片质量相等)的方案数,然后做一次树形背包DP(当前枚举到了u的子节点 v v f表示枚举到子节点 v v 之前的DP值):

f[u][i]=j,kf[u][j]×f[v][k]×ORZ

ORZ O R Z 表示 j j 段和k段合并成 i i 段的方案数。
如何求ORZ呢?
f[u] f [ u ] 的质量序列为 A A f[u]的质量序列为 B B f[v]的质量序列为 C C
A中的每一段可以只包含 B B 中的一段,可以只包含C中的一段,也可以有 B B C中各一段合并而成,但不能为空。特殊地, A A 的第一段只能包含节点u
相当于先枚举 B B 中的j1段在 A A 中放的位置,方案数为Ci1j1,然后把 C C 中的ij段放到 A A 中剩下的位置,使每一段都不为空。现在C中还剩下 ki+j k − i + j 个段,他们需要与 B B 中的段合并,方案数Cj1ki+j
所以:

ORZ=Cj1i1×Cki+jj1 O R Z = C i − 1 j − 1 × C j − 1 k − i + j

最后答案 if[n+1][i] ∑ i f [ n + 1 ] [ i ]
复杂度: f[u][i] f [ u ] [ i ] 第二维的上界只有 u u 的子树大小,枚举i相当于枚举 i i 的子树内的点。所以看上去是O(n4)的,实际上每对点都只在lca处被计算贡献了 O(n) O ( n ) 次,复杂度 O(n3) O ( n 3 )
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
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;
}
inline char get() {
    char c; while ((c = getchar()) != '<' && c != '='); return c;
}
const int N = 135, M = 265, ZZQ = 1e9 + 7;
int n, m, X[N], Y[N], fa[N], ecnt, nxt[M], adj[N], go[M], in[N], cnt[N],
f[N][N], sze[N], C[N][N], g[N];
bool eq[N], its[N];
void init() {
    int i, j; for (i = 0; i <= 120; i++) C[i][0] = 1;
    for (i = 1; i <= 120; i++) for (j = 1; j <= i; j++)
        C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % ZZQ;
}
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
int cx(int x) {
    if (fa[x] != x) fa[x] = cx(fa[x]);
    return fa[x];
}
bool zm(int x, int y) {
    int ix = cx(x), iy = cx(y);
    if (ix != iy) fa[iy] = ix;
    else return 1;
    return 0;
}
void dfs(int u, int fu) {
    int i, j, k; sze[u] = f[u][1] = 1;
    for (int e = adj[u], v; e; e = nxt[e]) {
        if ((v = go[e]) == fu) continue; dfs(v, u);
        for (i = 1; i <= n; i++) g[i] = 0;
        for (i = 1; i <= sze[u] + sze[v]; i++) for (j = 1; j <= sze[u]; j++)
        for (k = 1; k <= sze[v]; k++) {
            int x = k - i + j; if (x < 0) continue;
            (g[i] += 1ll * f[u][j] * f[v][k] % ZZQ *
                C[i - 1][j - 1] % ZZQ * C[j - 1][x] % ZZQ) %= ZZQ;
        }
        for (i = 1; i <= sze[u] + sze[v]; i++) f[u][i] = g[i];
        sze[u] += sze[v]; 
    }
}
int main() {
    int i; n = read(); m = read(); init();
    for (i = 1; i <= n; i++) fa[i] = i;
    for (i = 1; i <= m; i++) X[i] = read(),
        eq[i] = get() == '=', Y[i] = read();
    for (i = 1; i <= m; i++) if (eq[i]) zm(X[i], Y[i]);
    for (i = 1; i <= n; i++) its[in[i] = cx(i)] = 1;
    for (i = 1; i <= n; i++) fa[i] = i;
    for (i = 1; i <= m; i++)
        if (!eq[i]) {
            add_edge(in[X[i]], in[Y[i]]); cnt[in[Y[i]]]++;
            if (zm(in[X[i]], in[Y[i]])) return printf("0\n"), 0;
        }
    for (i = 1; i <= n; i++) if (its[i] && !cnt[i]) add_edge(n + 1, i);
    int ans = 0; dfs(n + 1, 0); for (i = 1; i <= sze[n + 1]; i++)
        ans = (ans + f[n + 1][i]) % ZZQ; cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值