Codeforces 995F Cowmpany Cowmpensation - 组合数学
很容易地能得到$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 }