[HEOI2013]SAO

[HEOI2013]SAO

  這道題是個不錯的計數題,考察了調換求和順序再前綴和優化,難點在狀態設計,比較考察思惟。
  一句話題意:給你一棵數,樹邊爲有向邊,求其拓撲序數。
  對DAG求拓撲數是一個NP問題,可是這裏保證是一棵樹,因此咱們能夠用樹形DP來求解。
  狀態的設計上,光設結點編號\(u\)不夠,還須要設計一維\(i\)表示結點\(u\)在以\(u\)爲根的子樹中的拓撲序的第\(i\)位,這樣咱們就能夠寫轉移方程了。
  對於\(u \rightarrow v\)
\[ F'[u][k] = \Sigma_{v\in son} F[u][i]\times F[v][j] \times \tbinom{k-1}{i-1} \times \tbinom{size_u+size_v-k}{size_u-i},i \leq k \leq i+j-1 \]
  對於\(u \leftarrow v\)
\[ F'[u][k] = \Sigma_{v\in son} F[u][i]\times F[v][j] \times \tbinom{k-1}{i-1} \times \tbinom{size_u+size_v-k}{size_u-i},i+j \leq k \leq size_v+i \]
  目標:
\[ \Sigma_{i=1}^{N} F[1][i] \]
  (全部結點的下標自動+1)
  發現三重循環鐵定不行,調換下\(j\)\(k\)的順序,發現\(F[v][j]\)能夠前綴和處理,削掉一維。
  事實上這樣作後總體的複雜度由\(\text{O}(N^3)\)降爲\(\text{O}(N^2)\)。(仔細研讀下面的代碼發現實際上處理次數爲點對數)
  細節看碼。c++

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define INF (1 << 30)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b);

inline int read() {
    int w = 0, f = 1; char c;
    while (!isdigit(c = getchar())) f = c == '-' ? -1 : f;
    while (isdigit(c)) w = (w << 3) + (w << 1) + (c ^ 48), c = getchar();
    return w * f;
}

inline int read_ch() {
    char c;
    while (c = getchar(), c != '>' && c != '<');
    return c == '<';
}

const int maxn = 1000 + 5;
const int MOD = 1e9 + 7;

struct Edge {
    int v, w, pre;
} e[maxn << 1];
int m, G[maxn];
void clear() {
    m = 0;
    memset(G, -1, sizeof(G));
}
void add(int u, int v, int w) {
    e[m++] = (Edge){v, w, G[u]};
    G[u] = m-1;
}

int T, N;
int f[maxn][maxn], g[maxn], C[maxn][maxn];

void inc(int &a, int b) {
    a += b;
    if (a >= MOD) a -= MOD;
}

int dec(int a) {
    if (a < 0) a += MOD;
    return a;
}

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

int size[maxn];

void dfs(int u, int fa) {
    size[u] = 1;
    f[u][1] = 1;
    for (register int i = G[u]; ~i; i = e[i].pre) { \\ 這裏實際上至關於u<->v之間的拓撲序合併起來
        int v = e[i].v;
        if (v == fa) continue;
        dfs(v, u);
        memcpy(g, f[u], sizeof(g));
        memset(f[u], 0, sizeof(f[u]));
        if (e[i].w) {
            for (register int i = 1; i <= size[u]; i++)
                for (register int k = i; k <= i+size[v]-1; k++)
                    inc(f[u][k], (ll)g[i] * dec(f[v][size[v]]-f[v][k-i]) % MOD * C[k-1][i-1] % MOD * C[size[u]+size[v]-k][size[u]-i] % MOD);
        } else {
            for (register int i = 1; i <= size[u]; i++)
                for (register int k = i+1; k <= size[v] + i; k++)
                    inc(f[u][k], (ll)g[i] * dec(f[v][k-i]) % MOD * C[k-1][i-1] % MOD * C[size[u]+size[v]-k][size[u]-i] % MOD);
        }
        size[u] += size[v];
    }
    for (register int i = 1; i <= size[u]; i++) inc(f[u][i], f[u][i-1]);
}

int main() {
    init();
    T = read();
    while (T--) {
        N = read();
        clear();
        for (register int i = 1; i < N; i++) {
            int u = read()+1, opt = read_ch(), v = read()+1;
            add(u, v, opt); add(v, u, !opt);
        }

        dfs(1, 1);

        printf("%d\n", f[1][N]);
    }

    return 0;
}
本站公眾號
   歡迎關注本站公眾號,獲取更多信息