• [HEOI2013]SAO


    可以发现题目给的是一颗有向树的形式,不论是有向树还是无向树,我们考虑 (dp) 转移基本是从子树转移到当前的父亲,状态也一般涉及的是在子树内部的状态,本题也不例外。

    我们钦定 (1) 为有向树的根,首先可以发现限制每个点位置的那些点一定是这个点在有向树上的儿子或者是父亲,并且只要确定限制它点的位置之后我们就能知道它能放的位置,因此我们就可以考虑令 (dp_{i, j}) 表示在以 (i) 为根的满足子树要求的序列(下面简称子树序列)中 (i) 的位置在 (j) 的方案数。对于 (u ightarrow v) 的边 (v)(u) 在有向树上的儿子,转移的化我们可以考虑枚举 (v) 所在其子树序列的位置 (k),以及 (k) 后面有 (l) 个位置插入到 (j) 之前,那么就有转移:

    [dp_{u, j + k} = dp_{u, j} imes sumlimits_{k = 1} ^ {s_v} dp_{v, k} sumlimits_{l = k} ^ {s_v} dbinom{l + j - 1}{j - 1} imes dbinom{s_v - l + s_u - j}{s_u - j} ]

    其中 (s_i) 表示以 (i) 为根的子树大小。上面的那个 (dp) 后面那一坨计算的东西貌似和 (k) 是没有关系的,可以考虑先枚举 (l) 试试:

    [dp_{u, j + k} = dp_{u, j} imes sumlimits_{k = 1} ^ {s_v} dbinom{k + j - 1}{j - 1} imes dbinom{s_v - k + s_u - j}{s_u - j} imes sumlimits_{l = 1} ^ k dp_{v, l} ]

    后面那一坨显然可以使用前缀和优化掉,这个 (dp) 的复杂度其实是 (O(n ^ 2)) 的,在 [HAOI2015]树上染色 提到了证明方法。

    同理我们可以得到 (v ightarrow u),其中 (v)(u) 的儿子的转移方程:

    [dp_{u, j + k} = dp_{u, j} imes sumlimits_{k = 0} ^ {s_v - 1} dbinom{k + j - 1}{j - 1} imes dbinom{s_v - k + s_u - j}{s_u - j} imes sumlimits_{l = k + 1} ^ {s_v} dp_{v, l} ]

    一样的记录前缀优化可以做到 (O(n ^ 2))

    一些坑点:

    • 以后树形背包直接使用滚动数组记录这一位和上一位的答案这样就不用纠结枚举顺序了。
    #include<bits/stdc++.h>
    using namespace std;
    #define N 1000 + 5
    #define Mod 1000000007
    #define rep(i, l, r) for(int i = l; i <= r; ++i)
    #define dep(i, l, r) for(int i = r; i >= l; --i)
    #define Next(i, u) for(int i = h[u]; i; i = e[i].next)
    struct edge{
        int v, next, w;
    }e[N << 1];
    char opt[N];
    int T, n, u, v, tot, h[N], s[N], dp[2][N][N], C[N][N], S[N][N];
    int read(){
        char c; int x = 0, f = 1;
        c = getchar();
        while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    void add(int u, int v, int w){
        e[++tot].v = v, e[tot].w = w, e[tot].next = h[u], h[u] = tot;
    }
    int Inc(int a, int b){
        return (a += b) >= Mod ? a - Mod : a;
    }
    int Dec(int a, int b){
        return (a -= b) < 0 ? a + Mod : a;
    }
    int Mul(int a, int b){
        return 1ll * a * b % Mod;
    }
    void dfs(int u, int fa){
        s[u] = 1, dp[0][u][1] = 1; int p = 1;
        Next(i, u){
            int v = e[i].v; if(v == fa) continue;
            dfs(v, u);
            if(!e[i].w){
                rep(j, 1, s[u]) rep(k, 1, s[v]){
                    int tmp = Mul(C[k + j - 1][j - 1], C[s[v] - k + s[u] - j][s[u] - j]);
                    dp[p][u][j + k] = Inc(dp[p][u][j + k], Mul(tmp, Mul(dp[p ^ 1][u][j], S[v][k])));
                }
            }
            else{
                rep(j, 1, s[u]) rep(k, 0, s[v] - 1){
                    int tmp = Mul(C[k + j - 1][j - 1], C[s[v] - k + s[u] - j][s[u] - j]);
                    dp[p][u][j + k] = Inc(dp[p][u][j + k], Mul(tmp, Mul(dp[p ^ 1][u][j], Dec(S[v][s[v]], S[v][k]))));
                }
            }
            p ^= 1;
            rep(j, 1, s[u]) dp[p][u][j] = 0;
            s[u] += s[v];
        }
        rep(i, 1, s[u]) S[u][i] = Inc(S[u][i - 1], dp[p ^ 1][u][i]);
    }
    int main(){
        T = read();
        rep(i, 0, N - 5) C[i][0] = 1;
        rep(i, 1, N - 5) rep(j, 1, i) C[i][j] = Inc(C[i - 1][j - 1], C[i - 1][j]);
        while(T--){
            n = read(), tot = 0;
            memset(h, 0, sizeof(h)), memset(dp, 0, sizeof(dp));
            rep(i, 1, n - 1){
                u = read() + 1, scanf("%s", opt + 1), v = read() + 1;
                if(opt[1] == '>') add(u, v, 0), add(v, u, 1);
                else add(u, v, 1), add(v, u, 0);
            }
            dfs(1, 0);
            printf("%d
    ", S[1][n]);
        }
        return 0;
    }
    
    GO!
  • 相关阅读:
    【强转】QEMU+GDB调试linux内核全过程
    从上往下打印二叉树
    栈的压入、弹出序列
    包含main函数的栈
    顺时针打印矩阵
    Linux终端美化
    Linux 终端美化
    KDE桌面环境自带的Konsole终端配置
    KDE美化及常用设置
    KDE常用桌面插件总结
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13486189.html
Copyright © 2020-2023  润新知