Loading

2022.10.10 总结

1. 洛谷 P1164(01 背包)

洛谷 P1164

题意

\(n\) 种菜,第 \(i\) 种菜卖 \(a_i\) 元,每种菜只有一份。

你口袋里有 \(m\) 元,你要把所有钱都用完,请问有多少种点菜方法。

思路

45 分

子集搜索,对于每种菜品,枚举是否选择。

时间复杂度

每种菜品都有两种转移,总共 \(n\) 种菜品。

总时间复杂度为 \(O(2 ^ n)\)

空间复杂度

变量记录方案数,\(O(1)\)

代码

void dfs(int t, int sum){
  if (t == n + 1) {
    cnt += sum == m;  // 判断相等
    return ;
  }
  dfs(t + 1, sum);  // 选
  dfs(t + 1, sum + a[t]);   // 不选
}

90 分

子集搜索 + 剪枝。判断钱数是否超过 \(m\) 元。

时间复杂度

每种菜品都有两种转移,总共 \(n\) 种菜品。

时间复杂度上限为 \(O(2 ^ n)\)

空间复杂度

变量记录方案数,\(O(1)\)

代码

void dfs(int t, int sum){
  if (sum > m) {   // 剪枝
    return ;
  }
  if (t == n + 1) {
    cnt += sum == m;
    return ;
  }
  dfs(t + 1, sum);
  dfs(t + 1, sum + a[t]);
}

100 分(记忆化搜索)

判断是否出现过这种状态,出现过,直接返回,否则,搜索。

时间复杂度

每种状态遍历一次,\(O(m \times n)\)

空间复杂度

数组记录状态是否出现过,\(O(m \times n)\)

代码

int S(int ans, int t){
  if (t == n + 1) {
    if (ans == m) {
      return 1;
    }
    return 0;
  }
  if (ans + a[t] <= m && !w[ans + a[t]][t + 1]) {   // 如果没有出现过并且状态合法
    f[ans + a[t]][t + 1] = S(ans + a[t], t + 1);
    w[ans + a[t]][t + 1] = 1;
  }
  if (!w[ans][t + 1]) {
    f[ans][t + 1] = S(ans, t + 1);
    w[ans][t + 1] = 1;
  }
  return f[ans + a[t]][t + 1] + f[ans][t + 1];
}

100 分(DP)

\(01\) 背包,先枚举钱数,再枚举每种菜。

状态:\(dp_{i, j}\) 表示当前买了第 \(j\) 种,花了 \(i\) 元的方案数。

转移方程:

\(dp_{i, j} = dp_{i, j - 1} (a_j > j)\)

\(dp_{i, j} = dp_{i, j - 1} + dp_{i - a_j, j - 1} (a_j \leq j)\)

初始状态:\(dp_{0, 0} = 1\)

目标状态:\(dp_{m, n}\)

时间复杂度

枚举钱数,\(O(m)\)

枚举每种菜,\(O(n)\)

总时间复杂度为 \(O(m \times n)\)

空间复杂度

\(dp\) 数组,\(O(m \times n)\)

代码

dp[0][0] = 1;  // 初始状态
for (int i = 0; i <= m; i++) {
  for (int j = 1; j <= n; j++) {
    dp[i][j] = dp[i][j - 1];
    if (a[j] <= i) {
      dp[i][j] += dp[i - a[j]][j - 1];  // 转移
    }
  }
}
cout << dp[m][n];  // 目标状态

2. 洛谷 P1077(多重背包)

洛谷 P1077

题意

总共有 \(n\) 种花,从 \(1\)\(n\) 编号,第 \(i\) 种花最多摆 \(a_i\) 盆,可以摆 \(m\) 盆花,摆花时,同一种花摆在一起,请你求出有多少种摆花方案。

思路

30 分

搜索 + 可行性剪枝。枚举每种花,再枚举这种花摆几盆。

时间复杂度

总共有 \(n\) 盆花,\(O(n)\)

\(i\) 种花可以摆 \(a_i\) 盆,\(O(a_i)\)

总时间复杂度为 \(O(n \times a_i)\)

空间复杂度

变量记录方案数,\(O(1)\)

代码

void dfs(int t, int ans){
  if (t > n + 1 || ans > m) {   // 可行性剪枝
    return ;
  }
  if (ans == m) {
    cnt = (cnt + 1) % MOD;
    return ;
  }
  for (int j = 0; j <= a[t]; j++) {  // 枚举盆数
    dfs(t + 1, ans + j);
  }
}

100 分(DP)

多重背包,枚举当前摆哪种花,再枚举一共摆了多少盆,最后枚举这种花摆了多少盆。

状态:\(dp_{i, j}\) 表示当前选择第 \(i\) 种花,总共摆了 \(j\) 盆。

转移方程:\(dp_{i, j} = \sum \limits _ {k = 1} ^ {min(j, a_i)} {dp_{i - 1, j - k}}\)

初始状态:\(dp_{0, 0} = 1\)

目标状态:\(dp_{n, m}\)

时间复杂度

枚举摆哪种花,\(O(n)\)

枚举一共摆多少盆,\(O(m)\)

枚举这种摆多少盆,\(O(a_i)\)

总时间复杂度为 \(O(n \times m \times a_i)\)

空间复杂度

二维 \(dp\) 数组记录方案数,\(O(n \times m)\)

代码

dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
  for (int j = 0; j <= m; j++) {
    for (int k = 0; k <= min(j, a[i]); k++) {
      dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % MOD;  // 转移
    }
  }
}
cout << dp[n][m];

3. 洛谷 P1616(完全背包)

洛谷 P1616

题意

\(m\) 种草药,每种草药都有采摘的时间和对应的价值。

你有 \(t\) 个单位的时间来采药,每种药都可以采无数个,请求出在规定时间内,最多可以采多少价值的草药。

思路

搜索

搜索 #1(50 分)

枚举每种草药采摘多少种,类似多重背包。

代码
void dfs(int i, int sumt, long long sumv) {
  if (i == m + 1) {
    ans = max(ans, sumv);
    return;
  }
  for (int j = 0; j <= (t - sumt) / a[i]; j++) {
    dfs(i + 1, sumt + j * a[i], sumv + 1ll * j * b[i]);
  }
}

搜索 #2(40 分)

对于第 \(i\) 种的某一次,有选与不选两种情况,并且选了之后还可以再选。

代码
void dfs(int i, int sumt, long long sumv) {
  if (i == m + 1) {
    ans = max(ans, sumv);
    return;
  }
  dfs(i + 1, sumt, sumv);              
  if (sumt + a[i] <= t) {            
    dfs(i, sumt + a[i], sumv + b[i]);  
  }
}

搜索 #3(40 分)

每次枚举选哪种草药,直到时间超过限制。

代码
void dfs(int sumt, long long sumv) {
  ans = max(ans, sumv);
  for (int i = 1; i <= m; i++) {  
    if (sumt + a[i] <= t) {
      dfs(sumt + a[i], sumv + b[i]);
    }
  }
}

时间复杂度

不好算,不算了。

空间复杂度

变量记录最大值,\(O(1)\)

100 分(DP)

记录在每个时刻可以采的最大价值。

状态:\(dp_i\) 表示在时刻 \(i\) 能采的草药的最大值

转移方程:

\(dp_i = max(dp_i, dp_{i - a_j} + b_j) (j \leq m)\)

初始状态:\(dp_i = 0\)

目标状态:\(dp_t\)

时间复杂度

枚举时间,\(O(t)\)

枚举草药,\(O(m)\)

总时间复杂度为 \(O(t \times m)\)

空间复杂度

一维 \(dp\) 数组记录时间,\(O(t)\)

代码

for (int i = 0; i <= t; i++) {
  for (int j = 1; j <= m; j++) {
    if (i >= a[j]) {
      dp[i] = max(dp[i], dp[i - a[j]] + b[j]);
    }
  }
}
cout << dp[t];

4. 洛谷 P1509

洛谷 P1509

题意

sqybi 有 \(m\) 元,\(r\) 的人品,他看中了 \(N\) 个 MM,编号为 \(1 \sim n\),他需要请其中一些 MM 吃饭。

\(i\) 号 MM 吃饭要花 \(rmb_i\) 大洋,\(rp_i\) 的人品,并且 sqybi 需要花 \(time_i\) 来搞定这个 MM。

请你求出 sqybi 泡最多的 MM 最少需要多少时间。

注意:sqybi 一次只能泡一个 MM。

思路

100 分

要解决这个问题,首先我们需要确定状态。

\((i, time, rmb, rp, cnt)\)

表示当前在泡第 \(i\) 个 MM,一共花了 \(time\) 个单位的时间,\(rmb\) 元,\(rp\) 的人品,已经泡到了 \(cnt\) 个 MM。

也就是:

\(dp_i, j, k\) 表示花 \(j\) 元,\(k\) 的人品去泡前 \(i\) 个 MM 能跑到的最多 MM 和花费的最少时间(先考虑数量,再考虑时间,用结构体实现)。

之后再确定转移。

\((i, time, rmb, rp, cnt) -> (i + 1, time, rmb, rp, cnt), (i + 1, time + t_i, rmb + rm_i, rp + r_i, cnt + 1)\)

所以转移方程就是:

\(dp_{i, j, k} = dp_{i - 1, j, k} (i \le n)\)

\(dp_{i, j, k} = max(dp_{i, j, k}, {dp_{i - 1, j - rmb_i, k - rp_i}.cnt + 1, dp_{i - 1, j - rmb_i, k - rp_i} + t_i}) (j \ge rmb_i, k \ge rp_i)\)

时间复杂度

\(n\) 个 MM,\(O(n)\)

\(m\) 元,\(O(m)\)

\(r\) 个人品,\(O(r)\)

总时间复杂度为 \(O(n \times m \times r)\)

空间复杂度

\(dp\) 数组记录数量和时间,\(O(n \times m \times r)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 110, RMB = 110, RP = 110, T = 1010 * N;

int n, rmb[N], rp[N], t[N], m, r;

struct C{  
  int cnt, t;  // 最多数量和最少时间
} dp[N][RMB][RP];

int main(){
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> rmb[i] >> rp[i] >> t[i];
  }
  cin >> m >> r;
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= m; j++) {
      for (int k = 0; k <= r; k++) {
        dp[i][j][k] = dp[i - 1][j][k];
        if (j >= rmb[i] && k >= rp[i]) {
          C x = dp[i - 1][j - rmb[i]][k - rp[i]];
          if (dp[i][j][k].cnt < x.cnt + 1) {  // 先考虑数量
            dp[i][j][k] = {x.cnt + 1, x.t + t[i]};
          } else if (dp[i][j][k].cnt == x.cnt + 1) {  // 再考虑时间
            dp[i][j][k].t = min(dp[i][j][k].t, x.t + t[i]);
          }
        }
      }
    }
  }
  cout << dp[n][m][r].t;
  return 0;
}
posted @ 2023-03-02 22:40  chengning0909  阅读(7)  评论(0编辑  收藏  举报