「CF995F」Cowmpany Cowmpensation

传送门

题目大意就是给你一颗有根树,每个点的权值可以是 \([1, D]\) 的任意一个数,需要满足一个节点的权值不比它的父亲的大,求不同赋值情况的方案数。

首先我们可以考虑一个比较显然的 \(O(nD)\) 做法:

考虑 \(\text{DP}\),设 \(dp_{u, j}\) 表示 \(u\) 的子树的方案,其中 \(u\) 的权值不超过 \(j\)

初始状态 \(dp_{u, j} = 1\),考虑每一颗 \(u\) 的子树 \(v\),我们每次都把 \(dp_{u, j}\) 乘上 \(dp_{v, j}\)。然后这些方案是有包含关系的,所以还要前缀和一下。

那么我们现在就可以求出 \(ans_i = dp_{1, i}, i \in [1, n]\)

可以证明 \(ans_i\) 是一个关于 \(i\) 的不超过 \(i + 1\) 次的多项式。

那么我们就直接拉格朗日插值一下就好了。

参考代码:

#include <cstdio>

const int _ = 3002, p = 1e9 + 7;

int tot, head[_]; struct Edge { int v, nxt; } edge[_];
inline void Add_edge(int u, int v) { edge[++tot] = (Edge) { v, head[u] }, head[u] = tot; }

int n, k, dp[_][_], ans;

inline int power(int x, int k) {
    int s = 1;
    for (; k; k >>= 1, x = 1ll * x * x % p)
        if (k & 1) s = 1ll * s * x % p;
    return s % p;
}

inline void dfs(int u) {
    for (int i = 1; i <= n; ++i) dp[u][i] = 1;
    for (int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].v; dfs(v);
        for (int j = 1; j <= n; ++j)
            dp[u][j] = 1ll * dp[u][j] * dp[v][j] % p;
    }
    for (int i = 1; i <= n; ++i) dp[u][i] = (dp[u][i] + dp[u][i - 1]) % p;
}

inline int Lagrange() {
    int ans = 0;
    for (int i = 0; i <= n; ++i) {
        int s1 = dp[1][i], s2 = 1;
        for (int j = 0; j <= n; ++j)
            if (i != j) {
                s1 = 1ll * s1 * ((k - j + p) % p) % p;
                s2 = 1ll * s2 * ((i - j + p) % p) % p;
            }
        ans = (ans + 1ll * s1 * power(s2, p - 2)) % p;
    }
    return ans;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("cpp.in", "r", stdin), freopen("cpp.out", "w", stdout);
#endif
    scanf("%d %d", &n, &k);
    for (int x, i = 2; i <= n; ++i) scanf("%d", &x), Add_edge(x, i);
    dfs(1);
    if (k <= n) printf("%d\n", dp[1][k]);
    else printf("%d\n", Lagrange());
    return 0;
}
posted @ 2020-06-15 22:26  Sangber  阅读(289)  评论(0编辑  收藏  举报