Codeforces 995F Cowmpany Cowmpensation - 组合数学

题目传送门

  传送点I

  传送点II

  传送点III

题目大意

  给定一个棵$n$个点的有根树和整数$D$,给这$n$个点标号,要求每个节点的标号是正整数,且不超过父节点的标号,根节点的标号不得超过D。

  很容易地能得到$O(nD)$的动态规划:设$f[i][j]$表示$i$号点标为$j$在它的子树内的方案数。

  写写它的转移方程:$f[i][j] = \prod_{s \in Son(i)}\sum_{k = 1}^{j} f[s][k]$。

  设$g[i][j]=\sum_{k = 1}^{j}f[i][k]$,那么转移方程就能写成:$f[i][j] = \prod_{s \in Son(i)} g[s][j]$。

  感觉很优美,考虑它有怎样的性质。

引理1 $f[i][j]$可以看成是关于$j$的多项式,其次数是$i$的子树大小减1,$g[i][j]$可以看成是关于$j$的多项式,其次数是$i$的子树大小。

  证明 考虑用归纳法。

  考虑叶节点,它的$f[i][j] = 1, g[i][j] = j$,因此对于叶节点成立。

  然后前一部分可以证明了。对于后一部分,因为$g[i]$是$f[i]$的前缀和函数,因此次数恰好比它大1(用差分)。

  然后后面的做法就很傻逼了。记录每个点$f, g$函数的在$1,\cdots,n + 1$处的取值,暴力计算。最后用逐差法插值,求$f[1][D]$。

  表示切完后才发现是Div 1。要不看标题,真以为是Div 2.

Code

 1 /**
 2  * Codeforces
 3  * Problem#995F
 4  * Accepted
 5  * Time: 140ms
 6  * Memory: 35400k
 7  */
 8 #include <iostream>
 9 #include <cstdlib>
10 #include <cstdio>
11 using namespace std;
12 typedef bool boolean;
13 
14 const int N = 3005, M = 1e9 + 7;
15 
16 void exgcd(int a, int b, int& x, int& y) {
17     if (!b)
18         x = 1, y = 0;
19     else {
20         exgcd(b, a % b, y, x);
21         y -= (a / b) * x;
22     }
23 }
24 
25 int inv(int a, int n) {
26     int x, y;
27     exgcd(a, n, x, y);
28     return (x < 0) ? (x + n) : (x);
29 }
30 
31 int n, D;
32 int fa[N];
33 int f[N][N];
34 
35 int add(int a, int b) {
36     a = a + b;
37     if (a >= M)
38         a -= M;
39     if (a < 0)
40         a += M;
41     return a;
42 }
43 
44 inline void init() {
45     scanf("%d%d", &n, &D);
46     for (int i = 2; i <= n; i++)
47         scanf("%d", fa + i);
48 }
49 
50 inline void solve() {
51     for (int i = 1; i <= n; i++)
52         for (int j = 1; j <= n + 1; j++)
53             f[i][j] = 1;
54     for (int i = n; i; i--) {
55         for (int j = 2; j <= n + 1; j++)
56             f[i][j] = add(f[i][j], f[i][j - 1]);
57         if (i > 1)
58             for (int j = 1; j <= n + 1; j++)
59                 f[fa[i]][j] = f[fa[i]][j] * 1ll * f[i][j] % M;
60     }
61     for (int i = 2; i <= n + 1; i++)
62         for (int j = 1; j <= n - i + 2; j++)
63             f[i][j] = add(f[i - 1][j + 1], -f[i - 1][j]);
64     int ans = 0, C = 1;
65     for (int i = 1; i <= n + 1 && i <= D; i++) {
66         ans = add(ans, C * 1ll * f[i][1] % M);
67         C = C * 1ll * (D - i) % M * inv(i, M) % M;
68     }
69     printf("%d\n", ans);
70 }
71 
72 int main() {
73     init();
74     solve();
75     return 0;
76 }
posted @ 2018-07-14 11:02  阿波罗2003  阅读(544)  评论(0编辑  收藏  举报