• DP的优化和根号分治: 跑步


    NOI-Online 2020 跑步

    题意

    求整数可重复划分方案数, (n leq 10^5).

    (50')

    状态 (f_{i, j}) 的表示数字 (i) 的划分中, 最大的数是 (j) 的方案数.

    5 5 2 1 就是一个包括在 (f_{13, 5}) 中的一个划分方案.

    [f_{i, j} = sum_{k = 1}^{k leq j} f_{i - j, k} ]

    每个状态 (f_{i, j}) 由所有 (f_{i - j, k},~k in [1, j]) 的总和组成. 可以理解成在 (f_{i - j, k}) 的每一个划分方案中加上一个元素 (j) 得到一个对应的 (i) 的划分.

    (O(n^2)) 的状态, (O(n)) 的转移, 复杂度 (O(n ^ 3)).

    for (register unsigned i(1); i <= n; ++i) {
      f[i][i] = 1;
    }
    for (register unsigned i(2); i <= n; ++i) {
      for (register unsigned j(1); j < i; ++j) {
        for (register unsigned k(1); k <= j; ++k) {
          f[i][j] += f[i - j][k];
          if(f[i][j] >= p) f[i][j] -= p;
        }
      }
    }
    for (register unsigned i(1); i <= n; ++i) {
      Ans += f[n][i];
      if(Ans >= p) Ans -= p;
    }
    

    (70')

    改变状态的定义, (f_{i, j}) 表示所有元素小于等于 (j)(i) 的方案数, 也就是 (50') 状态的前缀和.

    [f_{i, j} = f_{i, j - 1} + f_{i - j, j} ]

    这时转移可以理解为, (f_{i, j - 1}) 表示的单个元素小于等于 (j - 1), 组成 (i) 的方案中, 其元素一定小于 (j), 所以也应该统计入 (f_{i, j}) 中.

    (f_{i - j, j}) 则是将元素在 (j) 以内的 (i - j) 的划分中, 加入一个元素 (j) 得到的方案.

    这样可以把转移优化到 (O(1)), 状态仍是 (O(n^2)), 时间复杂度 (O(n^2)).

    f[1][1] = 1;
    for (register unsigned i(1); i <= n; ++i) {
      f[0][i] = 1;
    }
    for (register unsigned i(1); i <= n; ++i) {
      for (register unsigned j(1); j <= i; ++j) {
        f[i][j] = f[i][j - 1] + f[i - j][j];
        if(f[i][j] >= p) f[i][j] -= p;
      }
      for (register unsigned j(i + 1); j <= n; ++j) {
        f[i][j] = f[i][j - 1];
      }
      if(f[i][i] >= p) f[i][i] -= p;
    }
    printf("%u
    ", f[n][n]);
    

    (80')

    发现对于第二维只会用到 (j)(j - 1) 可以滚动数组, 只保留第一维, 对整个数组进行 (O(n)) 次转移.

    ((本次)f_i = (上次)f_i + (本次)f_{i - j})

    状态 (O(n^2)), 空间复杂度 (O(n)), 转移 (O(1)), 时间复杂度 (O(n^2)).

    之所以能多得 (10'), 是因为时空复杂度同阶的时候, 往往空间更容易炸, 于是优化了空间.

    f[0] = 1;
    for (register unsigned j(1); j <= n; ++j) {
      for (register unsigned i(j); i <= n; ++i) {
        f[i] += f[i - j];
        if(f[i] >= p) f[i] -= p;
      }
    }
    printf("%u
    ", f[n]);
    

    (100')

    根号分治, 设 (Sq = sqrt n), 先将所有 (j leq Sq)(f_i) 求出来, 这时的 (f_i) 表示单个元素不超过 (j)(i) 的划分方案数. (除了 (j) 的上界以外和 (80') 完全相同)

    n = RD(), p = RD(), Sq = sqrt(n) + 1;
    f[0] = 1;
    for (register unsigned j(1); j <= Sq; ++j) {
      for (register unsigned i(j); i <= n; ++i) {
        f[i] += f[i - j];
        if(f[i] >= p) f[i] -= p;
      }
    }
    

    然后再定义一个数组 (g_{i, j}), 表示包含 (i) 个非 (0) 元素的 (j) 的划分, 其中, (i leq Sq).

    [g_{i, j} = g_{i - 1, j - 1} + g_{i, j - i} ]

    转移也很简单 (g_{i - 1, j - 1})(i - 1) 个元素的 (j - 1) 的划分, 所有这种划分加上一个元素 (1) 就是一个 (i) 个元素, (j) 的划分. (g_{i, j - 1})(i) 个元素, (j - i) 的划分, 给这种划分的每个元素加 (1), 得到的就是一个最小元素大于 (1)(i) 个元素的 (j) 的划分. 这两种情况互不重复, 因为前者最小元素是 (1), 后者最小元素大于 (1). 也不会遗漏, 因为任何可行的划分都是可以由 (1) 的划分和这两种转移转移而来的.

    g[0][0] = 1;
    for (register unsigned i(1); i <= Sq; ++i) {
      for (register unsigned j(i); j <= n; ++j) {
        g[i][j] = g[i - 1][j - 1] + g[i][j - i];
        if(g[i][j] >= p) g[i][j] -= p;
      }
    }
    

    然后统计答案.

    我们把一个划分中的元素分成两类: (leq Sq)(> Sq).

    枚举 (i) 作为 (> Sq) 的元素个数, 因此 ((Sq + 1)i leq n), (i = O(sqrt n)).

    枚举 (j) 作为 (> Sq) 的元素总和, 因此 (j in [(Sq + 1)i, n]).

    对于每一个 (i), (j) 作为约束, 求合法 (n) 的划分方案数.

    分别讨论两种元素, 首先是 (leq Sq) 的元素, 它们的总和应该是 (n - j), 前面求出了 (f) 数组, 因此元素不大于 (Sq)(n - j) 的划分就是 (f_{n - j}).

    接下来是 (> Sq) 的元素, 因为每个元素都大于 (Sq), 所以我们可以只考虑它们比 (Sq) 多出来的部分. 所以相当于求包含 (i) 个元素的 (j - Sq imes i) 的划分, 也就是 (g_{i, j - Sq imes j}).

    因为两种元素对 (j)(n - j) 的划分中, 不同种类的元素大小一定不同, 所以互不干扰, 使用乘法原理统计答案即可:

    [Ans = sum_{i = 0}^{(Sq + 1)i leq n} Bigg( sum_{j = (Sq + 1)i}^{j leq n}igg(f_{n - j} imes g_{i, j - Sq imes i} igg) Bigg) ]

    for (register unsigned i(0); i * (Sq + 1) <= n; ++i) {
      for (register unsigned j(i * (Sq + 1)); j <= n; ++j) {
        Ans = ((unsigned long long)g[i][j - Sq * i] * f[n - j] + Ans) % p;
      }
    }
    

    对于 (f), 状态数 (O(nsqrt n)), 转移 (O(1)), 时间复杂度 (O(nsqrt n)).

    对于 (g), 状态数 (O(nsqrt n)), 转移 (O(1)), 时间复杂度 (O(nsqrt n)).

    对于 (Ans), 时间复杂度 (O(nsqrt n)).

    总复杂度 (O(n sqrt n)).

  • 相关阅读:
    Monkey稳定性测试实战之Monkey对APP随机测试
    自动化测试用例选型及手工对比自动化测试优缺点
    自动化测试如何解决验证码的问题
    Selenium家谱
    Python基础入门-数据类型
    with open
    Python开源机器学习框架:Scikit-learn六大功能,安装和运行Scikit-learn
    Python的可视化包 – Matplotlib 2D图表(点图和线图,.柱状或饼状类型的图),3D图表(曲面图,散点图和柱状图)
    python -- numpy 基本数据类型,算术运算,组合,分割 函数
    python pandas ---Series,DataFrame 创建方法,操作运算操作(赋值,sort,get,del,pop,insert,+,-,*,/)
  • 原文地址:https://www.cnblogs.com/Wild-Donkey/p/14960131.html
Copyright © 2020-2023  润新知