1. 程式人生 > 其它 >洛谷 P4099 [HEOI2013]SAO 題解

洛谷 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]+=\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;
}