66困难题:魔法甜点之和题解

101 阅读4分钟

题意

题目链接

  • n个甜点,每个甜点有一个价值,每个甜点可以选和不选;
  • m个魔法棒,每个魔法棒可以对选择的甜点使用至多一次,使它的价值变为原来的阶乘;
  • 期望总和S,即选择的甜点期望达到价值总和值S
  • 问题为在以上给定条件下,达到S的方案数

思路

简单分析及预处理

  • 首先根据题意估计一下时间复杂度,nmsn*m*s 须小于1e9 ,而阶乘在10!10!时就已经达到了1e6,故设定上限不超过10的阶乘3628800
  • 故此我们可以预处理1-10的阶乘以避免重复计算数组中的阶乘,对于10以上的返回3628800即可因为大概率用不上
  • 分析该题是计算方案数,由于有明显的递推性(选和不选,用和不用),首先考虑动态规划再进行优化

状态转移分析

  1. 状态定义及初始化
  • f[i][j][k]:表示前 i 个甜点中,当前拥有 j 个魔法棒,使得喜爱值之和为 k 的方案数。
  • f[i][j][0] = 1,表示不选择任何甜点,不使用任何魔法棒,喜爱值之和为 0 的方案数为 1。
  1. 状态转移
  • 对于每个甜点 i,我们可以选择累计到总和,或者不选择,选择后亦可选择使用魔法棒或者不使用。
  • 状态转移方程可以表示为:
f[i][j][k] = f[i-1][j][k] + f[i-1][j][k-like[i-1]] + f[i-1][j-1][k-getf(like[i-1])]

其中,f[i-1][j][k] 表示不选择第 i 个甜点,f[i-1][j][k-like[i-1]] 表示选择第 i 个甜点但不使用魔法棒,f[i-1][j-1][k-getf(like[i-1])] 表示选择第 i 个甜点并使用魔法棒。

image.png

  • 其中op数组表示预处理后对应的阶乘数组
  1. 边界分析
  • 当 k < like[i-1] 或 k < getf(like[i-1]) 时,对应的转移项不合法,需要跳过。

优化以及代码

  • 我们可以使用滚动数组来优化空间
#include <bits/stdc++.h>
using namespace std;

const int N = 110;
int f[2][N][N]; // 动态规划数组,f[i][j][k]表示前i个甜点中至多使用j个魔法棒,喜爱值之和为k的方案数
int p[N]; // 存储阶乘值的数组

// 预处理阶乘值
inline void getpre() {
  p[1] = 1; // 1的阶乘为1
  for (int i = 2; i <= 10; i++)
    p[i] = i * p[i - 1]; // 计算2到10的阶乘
}

// 获取甜点喜爱值的阶乘值
int getf(int x) {
  if (x <= 10)
    return p[x]; // 如果喜爱值小于等于10,直接返回对应的阶乘值
  return 362880; // 否则返回10的阶乘值(362880)
}

// 主函数,计算满足条件的方案数
int solution(int n, int m, int s, std::vector<int> like) {
  getpre(); // 预处理阶乘值
  vector<int> op(n); // 存储每个甜点的阶乘喜爱值
  for (int i = 0; i < n; i++) {
    op[i] = getf(like[i]); // 计算每个甜点的阶乘喜爱值
  }
  for (int i = 0; i <= n; i++) {
    for (int j = 0; j <= m; j++) {
      for (int k = 0; k <= s; k++) {
        f[i&1][j][k] = 0; // 初始化动态规划数组
        if (!k) { // 如果喜爱值之和为0
          f[i&1][j][k] = 1; // 方案数为1(不选任何甜点)
          continue;
        }
        if (i) { // 如果当前甜点数大于0
          f[i&1][j][k] += f[i - 1&1][j][k]; // 不选当前甜点
          if (k >= like[i - 1]) { // 如果当前喜爱值之和大于等于当前甜点的喜爱值
            f[i&1][j][k] += f[i - 1&1][j][k - like[i - 1]]; // 选当前甜点但不使用魔法棒
            if (j && k >= op[i - 1]) // 如果还有魔法棒且当前喜爱值之和大于等于当前甜点的阶乘喜爱值
              f[i&1][j][k] += f[i - 1&1][j - 1][k - op[i - 1]]; // 选当前甜点并使用魔法棒
          }
        }
      }
    }
  }
  return f[n&1][m][s]; // 返回前n个甜点中至多使用m个魔法棒,喜爱值之和为s的方案数
}

int main() {
  // 你可以添加更多测试用例
  std::vector<int> like1 = {1, 2, 3};
  std::vector<int> like2 = {1, 1, 1};
  std::vector<int> like3 = {1, 2, 3, 4, 5};
  std::cout << (solution(3, 2, 6, like1) == 5) << std::endl; // 测试样例1
  std::cout << (solution(3, 1, 1, like2) == 6) << std::endl; // 测试样例2
  std::cout << (solution(5, 3, 24, like3) == 1) << std::endl; // 测试样例3
  return 0;
}

求关注