[Heoi 2013] bzoj3167 SAO [树形dp]

29 篇文章 0 订阅
5 篇文章 0 订阅

Description:
一棵树,每条边上有一个不等号,求每个点填一个排列的数量。


Solution:
dp[u][i] d p [ u ] [ i ] 表示第 u u 个点在子树中排名为i的方案数,那么枚举新加入的点有多少个比 u u 小。
对于u<v的情况,有
dp[u][j+k]+=dp[u][j]C(k+j1,k)C(sz[u]+sz[v]jk,sz[v]k)ki=1dp[v][i] d p [ u ] [ j + k ] + = d p [ u ] [ j ] ∗ C ( k + j − 1 , k ) ∗ C ( s z [ u ] + s z [ v ] − j − k , s z [ v ] − k ) ∗ ∑ i = 1 k d p [ v ] [ i ]
枚举了 v v 中有多少个比u小,乘上对应 v v 所有可能的排名情况,然后把v u u 小的那部分和之前比u小的做归并,即 C(k+j1,k) C ( k + j − 1 , k ) ,后面则是后面一部分做归并。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1005, P = 1e9 + 7;
struct edge {
    int nxt, to, w;
} e[maxn * 2];
int n, cnt = 1;
int h[maxn], sz[maxn];
long long dp[maxn][maxn], sum[maxn][maxn], c[maxn][maxn], tmp[maxn];
void link(int u, int v, int w) {
    e[++cnt].nxt = h[u];
    h[u] = cnt;
    e[cnt].to = v;
    e[cnt].w = w;
}
void dfs(int u, int f) {
    sz[u] = 1;
    dp[u][1] = 1;
    sum[u][1] = 1;
    for(int i = h[u]; i; i = e[i].nxt) {
        int v = e[i].to;
        if(v != f) {
            dfs(v, u);
            memset(tmp, 0, sizeof(tmp));
            for(int j = sz[u]; ~j; --j) {
                for(int k = sz[v]; ~k; --k) {
                    tmp[j + k] = (tmp[j + k] + dp[u][j] * (e[i].w > 0 ? sum[v][k] : (sum[v][sz[v]] - sum[v][k] + P) % P) % P * c[k + j - 1][k] % P * c[sz[u] + sz[v] - j - k][sz[v] - k] % P) % P;
                }
            }
            sz[u] += sz[v];
            for(int j = 1; j <= sz[u]; ++j) {
                dp[u][j] = tmp[j];
            }
        }
    }
    for(int i = 1; i <= sz[u]; ++i) {
        sum[u][i] = (sum[u][i - 1] + dp[u][i]) % P;
    }
}
int main() {
    int T;
    scanf("%d", &T);
    c[0][0] = 1;
    for(int i = 1; i < maxn; ++i) {
        c[i][0] = 1;
        for(int j = 1; j <= i; ++j) {
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % P;
        }
    }
    while(T--) {
        cnt = 1;
        memset(h, 0, sizeof(h));
        memset(dp, 0, sizeof(dp));
        memset(sum, 0, sizeof(sum));
        scanf("%d", &n);
        for(int i = 1; i < n; ++i) {
            int u, v, w;
            char C[10];
            scanf("%d%s%d", &u, C, &v);
            ++u;
            ++v;
            if(C[0] == '>') {
                w = 1;
            } else {
                w = -1;
            }
            link(u, v, w);
            link(v, u, -w);
        }
        dfs(1, 0);
        printf("%lld\n", sum[1][n]);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值