习题课3-1(动态规划)

习题课3-1

数字三角形

  • 一个三角形(金字塔状)有n行,第i行有i个数字,数字错位排列

  • 求出从顶端走到底端的一条路径,满足路径上的数字之和最大,同时在路径上行走时,每次只能走到下一行相邻的数字上

  • 4
    1
    2 3
    4 5 6
    7 8 9 10
    
  • 输出20

  • 每一次都要向下走一行,同时只能走到相邻两个点上,不会走回头路

  • 每行只会经过一个点

  • 在三角形某个位置(i,j)上,可以发现只有两个点可以走进该点,即(i-1,j),(i-1,j-1)

解法1

  • 写一个搜索,从顶端向下找出所有到达底端的路径

  • 会有大量重复

解法2

  • a(i,j)表示位置(i,j)上的数字,d(i,j)能得到的最优路径之和

  • 某一个点的d(i,j),只能是d(i-1,j)和d(i-1,j-1)走过来,找出其大者,同时加上当前点a(i,j),即可写出状态转移方程

  • d ( i , j ) = m a x { d ( i − 1 , j − 1 ) , d ( i − 1 , j ) } + a ( i , j ) d(i,j) = max\{d(i-1,j-1),d(i-1,j)\} +a(i,j) d(i,j)=max{d(i1,j1),d(i1,j)}+a(i,j)

  • i-1 j-1有可能越界,在外面包一层0,取max的时候自动就会取里面的数字非0的数字

  • 最后答案就是:

  • m a x { d ( n , j ) ∣ 1 ≤ i ≤ n } max\{d(n,j)|1\leq i\leq n\} max{d(n,j)1in}

  • 即,动态规划

  • 不会走到重复的地方->无后效性->有向无环图

  • 每次只有两个决策->选一个最优的决策->全局最优

  • 最后在底端一行里面比较出最大者

  • 时间复杂度,每一次取了3次常数O(3) = O(1),状态树一共有n的平方个状态,所以就是O(n2)

背包问题1

  • n种物品,每种物品有相应的价值和体积,物品分为两类,一类是单个物品,即该物品只有一个,一类是多个物品,即该物品有无限个
  • 给定一个体积为V的背包,求一种装填方案使得价值之和最大

问题分析

  • 单个物品用经典01背包
  • 无限物品,即完全背包

解法1

01背包
  • 令f(i,j)表示前i个物品,用一个容量为j的背包能装下物品(不能超过容量j,最大等于j)的最大价值之和

  • 体积为v,价值为w的物品i,然后对于容量为j的背包

  • f ( i , j ) = m a x { f ( i − 1 , j ) , f ( i − 1 , j − v ) + w } f(i,j) = max \{f(i-1,j),f(i-1,j-v)+w\} f(i,j)=max{f(i1,j),f(i1,jv)+w}

  • 对于某个物品,两个策略就是放与不放

  • 不放就是f(i-1,j),放的话就是f(i-1,j-v)+w

完全背包
  • 类似于01背包

  • f ( i , j ) = m a x { f ( i − 1 , j ) , f ( i , j − v ) + w } f(i,j) = max \{f(i-1,j),f(i,j-v)+w\} f(i,j)=max{f(i1,j),f(i,jv)+w}

  • f(i,j-v):前i个物品,容量为j-v的背包,也就是说我们可能已经在这个j-v的背包里放过一些物品i了,这就包含了无限物品的意思。

  • 更详细的解释:将无限个物品拆成一个一个的,然后做01背包,假如第a个物品到第b个物品都是我们拆的物品(完全相同的而且足够多),那么根据01背包,我们有:

  • KaTeX parse error: Undefined control sequence: \ at position 54: …},a\leq i\leq b\̲ ̲

  • 我们将f(a…)、f(a+1…)、…、f(b,…)这些数组取最大值和到一起

滚动数组

  • 01背包更新时只用到了<i的值,所以可以重复利用信息

  • 01背包可以这些写(倒序枚举j)

  • f ( i , j ) = m a x { f ( j ) , f ( j − v ) + w } f(i,j) = max \{f(j),f(j-v)+w\} f(i,j)=max{f(j),f(jv)+w}

  • 倒序枚举计算时,在枚举物品i时,在倒序计算时,体积j时从后往前更新最大价值,此时后面的值等于前面的值加上物品i的价值,而前面的最大价值没有考虑加入物品i的情况,所以只考虑加入一次的情况

  • 完全背包可以这样写(顺序枚举j)

  • f ( i , j ) = m a x { f ( j ) , f ( j − v ) + w } f(i,j) = max \{f(j),f(j-v)+w\} f(i,j)=max{f(j),f(jv)+w}

  • 顺序枚举时,在枚举物品i是,背包体积是从前往后更新最大价值,此时后面的值更新时,前面的最大价值计算已经考虑了加入1,2,…,若干件i的情况,此时就考虑了这个背包装满时最大能装物品i的情况,也就是无穷物品

  • 得到的结果和二维结果一样

public class beibaowenti1_3_1 {

    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        Task solver = new Task();
        solver.solve(in, out);
        out.close();
    }

    static class Task {

        // ================= 代码实现开始 =================

        /* 请在这里定义你需要的全局变量 */
        final int N = 5005;
        int[] f = new int [N];

        // n:物品个数
        // V:背包的体积
        // t:长度为n的数组,第i个元素若为0,表示物品i为单个物品;若为1,表示物品i为多个物品。(i下标从0开始,下面同理)
        // w:长度为n的数组,第i个元素表示第i个物品的价值
        // v:长度为n的数组,第i个元素表示第i个物品的体积
        // 返回值:最大价值之和
        int getAnswer(int n, int V, List<Integer> t, List<Integer> w, List<Integer> v) {
            /* 请在这里设计你的算法 */
            for (int i = 0; i < n; i++) {
                if (t.get(i) == 0) {
                    // 01背包
                    // 倒序计算不具备累加性,每种物品只算了一次
                    // f[5] = max(f[5],f[5-v(0)]+w(0))对于体积为5的背包,只算了装一次序号0物品的情况
                    for (int j = V; j >=v.get(i) ; j--) {
                        f[j] = Math.max(f[j],f[j-v.get(i)]+w.get(i));
                    }
                }else {
                    // 完全背包
                    // 正序计算具备累加性,物品可以算任意次,只要不超过背包体积
                    // f[v(0)] = max(f[v(0)],f[v(0)-v(0)]+w(0))
                    // f[v(0)+v(0)] = max(f[v(0)+v(0)],f[v(0)]+w(0))
                    for (int j = v.get(i); j <=V ; j++) {
                        f[j] = Math.max(f[j],f[j-v.get(i)]+w.get(i));
                    }
                }
            }
            return f[V];
        }

        // ================= 代码实现结束 =================

        void solve(InputReader in, PrintWriter out) {
            int n = in.nextInt(), V = in.nextInt();
            List<Integer> T = new ArrayList<>();
            List<Integer> W = new ArrayList<>();
            List<Integer> _V = new ArrayList<>();
            for (int i = 0; i < n; ++i) {
                int t = in.nextInt(), w = in.nextInt(), v = in.nextInt();
                T.add(t);
                W.add(w);
                _V.add(v);
            }
            out.println(getAnswer(n, V, T, W, _V));
        }

    }

拓展

  • 01背包,选与不选
  • 完全背包,每个物品有无限个,想选多少选多少
  • 多重背包,物品不是有限个,但是有多个,同样是想选多少选多少

背包问题2

  • n个物品,每个物品有一个体积和价值
  • q次询问,若把物品x丢弃,剩下的物品装进大小为V的背包得到的最大价值是多少?

问题分析

  • 把剩下的物品直接拿来做01背包,可以通过一部分数据

  • 把容量为V的背包,拆分成两个背包,体积分别是V1、V2(V1+V2=V)

  • 假若我们将一堆物品分成了两份,然后分别将每一份撞到拆出来的背包里,同时求出最优解,那么

  • A n s ( V ) = m a x { A n s 1 ( V 1 ) + A n s 2 ( V 2 ) ∣ V 1 + V 2 = V , V 1 , V 2 > = 0 } Ans(V) = max\{Ans1(V1)+Ans2(V2)|V1+V2=V,V1,V2>=0\} Ans(V)=max{Ans1(V1)+Ans2(V2)V1+V2=V,V1,V2>=0}

  • 对应到题目,如果丢弃的是x,x序号之前的物品用v1背包装,x序号之后的物品用v2背包装

public class beibaowenti2_3_1 {

    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        Task solver = new Task();
        solver.solve(in, out);
        out.close();
    }

    static class Task {

        // ================= 代码实现开始 =================
        final int N = 5005;

        int[][] d = new int[N][N];
        int[][] f = new int[N][N];

        /* 请在这里定义你需要的全局变量 */

        // n个物品,每个物品有体积价值,求若扔掉一个物品后装进给定容量的背包的最大价值
        // n:如题
        // w:长度为n+1的数组,w.get(i)表示第i个物品的价值(下标从1开始,下标0是一个数字-1,下面同理)
        // v:长度为n+1的数组,v.get(i)表示第i个物品的体积
        // q:如题
        // qV:长度为q+1的数组,qV.get(i)表示第i次询问所给出的背包体积
        // qx:长度为q+1的数组,qx.get(i)表示第i次询问所给出的物品编号
        // 返回值:返回一个长度为q的数组,依次代表相应询问的答案
        List<Integer> getAnswer(int n, List<Integer> w, List<Integer> v, int q, List<Integer> qV, List<Integer> qx) {
            /* 请在这里设计你的算法 */
            // 丢弃的x之前的最大价值背包
            // 枚举n个物品
            // 计算前缀背包
            for (int i = 1; i <= n; i++) {
                // 由于是二维数组,需要做充填备忘
                // 对于i=1时,d[1][j] = d[0][j]
                // 对于i=2时,d[2][j] = d[1][j]
                for (int j = 0; j < v.get(i); j++) {
                    d[i][j] = d[i-1][j];
                }
                // 01背包问题
                // 对于体积为v,价值为w的物品i,要装填到背包体积为j的背包中的最大价值
                // d[i-1][j]表示不放物品i,只放i之前的物品时所能装的最大价值
                // d[i-1][j-v.get(i)]+w.get(i) 表示放入物品i后,恰好装满背包,此时由没放物品i,背包体积为当前体积减去物品i体积的最大价值背包加上物品i的价值
                // 两者中较大者即为最大价值
                for (int j = v.get(i); j <= 5000; j++) {
                    d[i][j] = Math.max(d[i-1][j],d[i-1][j-v.get(i)]+w.get(i));
                }
            }

            // 丢弃的x之后的最大价值背包
            // 与上面的过程相反,上面是依照物品正序求最大背包
            // 此处依照物品顺序倒序求最大背包
            // 计算后缀背包
            for (int i = n; i >= 1; i--) {
                for (int j = 0; j < v.get(i); j++) {
                    f[i][j] = f[i+1][j];
                }
                for (int j = v.get(i); j <= 5000; j++) {
                    f[i][j] = Math.max(f[i+1][j],f[i+1][j-v.get(i)]+w.get(i));
                }
            }

            List<Integer> ans = new ArrayList<>();
            for (int k = 1; k <= q; k++) {
                int x = qx.get(k),V = qV.get(k);
                // 最大值,将背包拆分成x之前的背包,和x之后的背包,所有组合中的背包和
                int mx = 0;
                for (int i = 0; i <= V; i++) {
                    mx = Math.max(mx,d[x-1][i]+f[x+1][V-i]);
                }
                ans.add(mx);
            }
            return ans;
        }

        // ================= 代码实现结束 =================

        void solve(InputReader in, PrintWriter out) {
            int n, q;
            List<Integer> v = new ArrayList<>();
            List<Integer> w = new ArrayList<>();
            List<Integer> qv = new ArrayList<>();
            List<Integer> qx = new ArrayList<>();
            v.add(-1);
            w.add(-1);
            qv.add(-1);
            qx.add(-1);
            n = in.nextInt();
            for (int i = 0; i < n; ++i) {
                int a = in.nextInt(), b = in.nextInt();
                v.add(a);
                w.add(b);
            }
            q = in.nextInt();
            for (int i = 0; i < q; ++i) {
                int a = in.nextInt(), b = in.nextInt();
                qv.add(a);
                qx.add(b);
            }
            List<Integer> ans = getAnswer(n, w, v, q, qv, qx);
            for (int i = 0; i < q; ++i)
                out.println(ans.get(i));
        }

    }

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值