洛谷 P4099 [HEOI2013]SAO 题解

一、题目:

洛谷原题

二、思路:

考虑树形 DP。

DP 状态为 \(f[x,k]\) 表示在 \(x\) 这棵子树中,\(x\) 的拓扑序的位置是 \(k\) 的方案数。

那么设接下来考虑的儿子是 \(y\)\(tmpf[k]\) 表示考虑了 \(y\) 之前的儿子,\(x\) 的拓扑序的位置是 \(k\) 的方案数。

假设当前我们要用 \(tmpf[p_1]\)\(f[y,p_2]\) 去更新 \(f[x,p_3]\)

那么分为两种情况。

  1. \(x\) 的拓扑序在 \(y\) 之前。

    1. 首先来确定 \(p_3\) 的范围。我们会发现,\(p_3\) 至少是 \(p_1\);又因为 \(y\) 的子树中有 \(p_2-1\) 个节点既可以排在 \(x\) 前,也可以排在 \(x\) 后,所以 \(p_3\) 最大是 \(p_1+p_2-1\)。即 \(p_1\leq p_3\leq p_1+p_2-1\)

    2. 接下来考虑转移。在 \(p_3\) 前面,有 \(p_3-1\) 个空位,选出 \(p_1-1\) 个来让 \(x\) 原来的那些拓扑序在 \(x\) 之前的子孙填;在 \(p_3\) 后面,有 \(siz_x+siz_y-p_3\) 个空位,选出 \(siz_x-p_1\) 个来让 \(x\) 原来的那些拓扑序在 \(x\) 之后的子孙填。

      \[f[x,p_3]\overset{+}\gets \dbinom{p_3-1}{p_1-1}\dbinom{siz_x+siz_y-p_3}{siz_x-p_1}tmpf[p_1]\times f[y,p_2] \]

      有些同学可能会问,你只决定了这些点的位置就可以确定整棵子树的拓扑序吗?答案是肯定的。试想,\(x\) 原来的那些子孙的在拓扑序中相对位置是已经确定下来的,\(y\) 的子孙也是。那么我们填完 \(x\) 原本的子孙后,从第一位依次向后找空位,先将 \(y\) 的子树中排在 \(y\) 之前的 \(p_2-1\) 个子孙填满,再将后 \(siz_y-p_2\) 个子孙填满。也就是说,\(x\) 的子孙一旦确定了位置,整棵子树的拓扑序也就相应确定了。

  2. \(x\) 的拓扑序在 \(y\) 之后。

    1. \(p_1+p_2\leq p_3\leq p_1+siz_y\)
    2. 转移同上。

注意到转移中只有一处出现了 \(p_2\),所以可以对 \(f[y,p_2]\) 使用前缀和优化。具体实现见代码。

三、代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
#define FILEIN(s) freopen(s, "r", stdin)
#define FILEOUT(s) freopen(s, "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int MOD = 1000000007, MAXN = 1005;

int n, head[MAXN], tot;
int siz[MAXN];
long long f[MAXN][MAXN], tmpf[MAXN];
long long C[MAXN][MAXN];

struct Edge {
    int y, next, w;
    Edge() {}
    Edge(int _y, int _next, int _w) : y(_y), next(_next), w(_w) {}
}e[MAXN << 1];

inline void connect(int x, int y, int w) {
    e[++ tot] = Edge(y, head[x], w);
    head[x] = tot;
}

void dfs(int x, int fa) {
    siz[x] = 1;
    f[x][1] = 1;
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].y;
        if (y == fa) continue;
        dfs(y, x);

        memcpy(tmpf, f[x], sizeof tmpf);
        mem(f[x], 0);

        if (e[i].w == 0) {
            for (int p1 = 1; p1 <= siz[x]; ++ p1) {
                for (int p3 = p1; p3 <= p1 + siz[y] - 1; ++ p3) {
                    (f[x][p3] += C[p3 - 1][p1 - 1] * C[siz[x] + siz[y] - p3][siz[x] - p1] % MOD * tmpf[p1] % MOD * (f[y][siz[y]] - f[y][p3 - p1]) % MOD) %= MOD;
                    if (f[x][p3] < 0) f[x][p3] += MOD;
                }
            }
        }
        else {
            for (int p1 = 1; p1 <= siz[x]; ++ p1) {
                for (int p3 = p1 + 1; p3 <= p1 + siz[y]; ++ p3) {
                    (f[x][p3] += C[p3 - 1][p1 - 1] * C[siz[x] + siz[y] - p3][siz[x] - p1] % MOD * tmpf[p1] % MOD * f[y][p3 - p1] % MOD) %= MOD;
                }
            }
        }

        siz[x] += siz[y];
    }
    for (int p3 = 2; p3 <= siz[x]; ++ p3) (f[x][p3] += f[x][p3 - 1]) %= MOD;
}

inline void prework(void) {
    C[0][0] = 1;
    for (int i = 1; i <= 1000; ++ i) {
        C[i][0] = 1;
        for (int j = 1; j <= i; ++ j)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
    }
}

int main() {
    prework();
    int T = read();
    while (T --) {
        tot = 0; mem(head, 0); mem(f, 0); mem(siz, 0);
        n = read();
        for (int i = 1; i < n; ++ i) {
            int x = read() + 1; char op[3]; scanf("%s", op); int y = read() + 1;
            connect(x, y, (*op) == '>');
            connect(y, x, (*op) == '<');
        }
        dfs(1, 0);
        printf("%lld\n", f[1][siz[1]]);
    }
    return 0;
}

posted @ 2021-07-02 21:04  蓝田日暖玉生烟  阅读(55)  评论(0编辑  收藏  举报