zoukankan      html  css  js  c++  java
  • 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
    ", ans);
    70 }
    71 
    72 int main() {
    73     init();
    74     solve();
    75     return 0;
    76 }
  • 相关阅读:
    简单明了的带你理解springboot原理和三大核心注解
    Spring Boot(一):入门篇
    【Mysql优化】聚簇索引与非聚簇索引概念
    Mysql索引原理与优化
    Mysql全文索引的使用
    索引的优缺点,如何创建索引
    184 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 04 例:字符串与byte(即:字节)数组间的相互转换
    183 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 03 String常用方法(下)
    182 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 02 String常用方法(上)
    181 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 01 String常用方法简介
  • 原文地址:https://www.cnblogs.com/yyf0309/p/9308869.html
Copyright © 2011-2022 走看看