zoukankan      html  css  js  c++  java
  • 「考前日志」11.12

    • 线性DP
    • 背包DP

    xxdp

    AcWing276

    在动态规划问题需要给出方案时,通常做法是额外使用一些与DP状态大小相同的数组记录下来每个状态的“最优解”是从何处转移而来的。最终用 DP 求出最优解后,通过一次递归,沿着记录的每一步“转移来源”回到初态,即可得到一条从初态到最优解的转移路径,也就是所求的具体方案。

    AcWing277 饼干

    题目分析

    比较巧妙的转化,但是输出方案的时候出了问题,迫使我看了y总的输出方案代码……不知道自己的为啥不行,放坑了

    首先一个性质:贪婪度越大的孩子获得的饼干数应该越多。证明也不难证,直接用贪心中的临项交换法就行了,不再赘述。因此我们可以把小朋友按照贪婪值从大到小排序,这样之后他们分配到的饼干数量是单调递减的。

    状态设计:设 (f_{i,j}) 表示前 (i) 个小朋友分了 (j) 块饼干所得到的最小怨气值总和。

    状态转移:

    • 如果第 (i) 个小朋友获得的饼干数不为 (1)(j>=i),那么 (f_{i,j}) 的一个可行选择为 (f_{i,j-i}),这两个式子是等价的,前 (i) 个小朋友分了 (j) 块饼干等价于前 (i) 个小朋友分了 (j-i) 块饼干,原因是这样相当于每个人少拿一块饼干,但是获得的饼干数量的相对顺序是不变的,所以怨气值之和也是不会变的。
    • 如果第 (i) 个小朋友获得的饼干数为 (1),那么就可以枚举前面有多少个小朋友获得的饼干数为 (1),从中取最小值,这一步可以用前缀和优化。

    由此可得整个DP的转移方程为:

    [f_{i,j}=minegin{cases}f_{i,j-i}& ext{if } jge i\minlimits_{k=0}^{i-1}(f_{k,j-(i-k)}+k imessumlimits_{x=k+1}^{i}g_x)& ext{if }jge(i-k)end{cases} ]

    初始条件为 (f_{0,0}=0),最终目标为 (f_{n,m})

    输出方案有点迷……

    代码

    #include <cmath>
    #include <queue>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #define pii pair <int, int>
    using namespace std;
    
    const int A = 33;
    const int B = 5011;
    const int mod = 1e9 + 7;
    const int inf = 0x3f3f3f3f;
    
    inline int read() {
      char c = getchar();
      int x = 0, f = 1;
      for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
      for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
      return x * f;
    }
    
    pii g[A];
    int n, m, f[A][B], sum[A], ans[A];
    
    int main() {
      n = read(), m = read();
      for (int i = 1; i <= n; i++) {
        g[i].first = read();
        g[i].second = i;
      }
      sort(g + 1, g + 1 + n);
      reverse(g + 1, g + 1 + n);
      for (int i = 1; i <= n; i++) 
        sum[i] = sum[i - 1] + g[i].first;
      memset(f, inf, sizeof(f));
      f[0][0] = 0;
      for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
          if (j >= i) f[i][j] = f[i][j - i];
          for (int k = 0; k < i && j >= (i - k); k++)
            f[i][j] = min(f[i][j], f[k][j - (i - k)] + k * (sum[i] - sum[k]));
        }
      }
      cout << f[n][m] << '
    ';
      int i = n, j = m, h = 0;
      while (i && j) {
        if (j >= i && f[i][j] == f[i][j - i]) j -= i, h++;
        else {
          for (int k = 1; k <= i && k <= j; k++) {
            if (f[i][j] == f[i - k][j - k] + (i - k) * (sum[i] - sum[i - k])) {
              for (int x = i; x > i - k; x--) ans[g[x].second] = 1 + h;
              i -= k, j -= k;
              break;
            }
          }
        }
      }
      for (int i = 1; i <= n; i++) cout << ans[i] << " ";
      puts("");
      return 0;
    }
    

    背包DP

    比较简单了,随便写写

    0/1背包

    (n) 件物品和一个容量为 (M) 的背包。第 (i) 件物品的体积是 (V_i),价值是 (W_i)。求解将哪些物品装入背包且容量不超过 (M) 可使价值总和最大。

    (f_{i,j})表示前 (i) 件物品恰放入一个容量为 (j) 的背包可以获得的最大价值,转移方程为

    [f_{i,j}=maxegin{cases}f_{i-1,j}\f_{i-1,j-V_i}+W_i& ext{if }{j}ge{V_i}end{cases} ]

    初始化 (f_{0,0}=0),目标为 (maxlimits_{i=0}^{m}{f_{n,i}})

    for (int i = 1; i <= n; i++) {
      for (int j = 0; j <= m; j++) {
        if (j < v[i]) f[i][j] = f[i - 1][j];
        else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
      }
    }
    

    可以用滚动数组优化空间。

    int f[2][maxn_M+1];
    int now = 0, last = 1;
    for (int i = 1; i <= n; i++) {
      swap(now, last);
      for (int j = 0; j <= m; j++) {
        if (j < v[i]) f[now][j] = f[last][j];
        else f[now][j] = max(f[last][j], f[last][j - v[i]] + w[i]);
      }
    }
    

    其实可以直接压掉第一维,此时第二维需要使用倒序枚举的方法。

    我是代码
    我是01背包压维的代码
    

    AcWing278 数字组合

    01背包板子题。

    #include <cmath>
    #include <queue>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    const int A = 1e5 + 11;
    const int B = 1e6 + 11;
    const int mod = 1e9 + 7;
    const int inf = 0x3f3f3f3f;
    
    inline int read() {
      char c = getchar();
      int x = 0, f = 1;
      for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
      for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
      return x * f;
    }
    
    int n, m, f[A], a[A];
    
    int main() {
      n = read(), m = read();
      f[0] = 1;
      for (int i = 1; i <= n; i++) a[i] = read();
      for (int i = 1; i <= n; i++) 
        for (int j = m; j >= a[i]; j--) f[j] += f[j - a[i]]; 
      cout << f[m] << "
    ";
      return 0;
    }
    

    完全背包

    (n) 种物品和一个容量为 (M) 的背包。每种物品都有无限个,第 (i) 种物品的体积是 (V_i),价值是 (W_i)。求解将哪些物品装入背包且容量不超过 (M) 可使价值总和最大。

    (f_{i,j})表示前 (i) 件物品恰放入一个容量为 (j) 的背包可以获得的最大价值,转移方程为

    [f_{i,j}=maxegin{cases}f_{i-1,j}\f_{i,j-V_i}+W_i& ext{if }{j}ge{V_i}end{cases} ]

    初始化 (f_{0,0}=0),目标为 (maxlimits_{i=0}^{m}{f_{n,i}})

    同样可以压掉一维,但是正序枚举就可以了,因为一个物品可以选多次。

    int f[100010], n, m, v[A], w[A];
    for (int i = 1; i <= n; i++) 
      for (int j = v[i]; j <= m; j++) 
        f[j] = max(f[j], f[j - v[i]] + w[i]);
    int ans = 0;
    for (int i = 0; i <= m; i++) ans = max(ans, f[i]);
    cout << ans << '
    ';
    

    AcWing279 自然数拆分

    还是板子题……

    #include <cmath>
    #include <queue>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #define int long long
    using namespace std;
    
    const int A = 1e5 + 11;
    const int B = 1e6 + 11;
    const int mod = 2147483648;
    
    inline int read() {
      char c = getchar();
      int x = 0, f = 1;
      for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
      for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
      return x * f;
    }
    
    int n, f[A];
    
    signed main() {
      n = read();
      f[0] = 1;
      for (int i = 1; i < n; i++) {
        for (int j = i; j <= n; j++) {
          f[j] = (f[j] + f[j - i]) % mod;
        }
      }
      cout << f[n] << '
    ';
      return 0;
    }
    
  • 相关阅读:
    名种样式的加入收藏和设为主页代码
    Android蓝牙UUID
    Discuz (1040) notconnect错误的解决办法
    IIS上配置404页面的图文教程
    C#操作excel(多种方法比较)
    Server Application Unavailable出现的原因及解决方案集锦
    怎么在google player下载apk
    apk反编译|android程序反编译
    discuz x2.5帖子无法访问|discuz x2.5 帖子报错500
    C#实现路由器断开连接,更改公网ip
  • 原文地址:https://www.cnblogs.com/loceaner/p/13962078.html
Copyright © 2011-2022 走看看