Google KickStart RoundB Energy Stones

在看下面的题目之间,推荐阅读这篇博客,把0,1背包问题讲解的更加通俗易懂一些。0,1背包

Energy Stones

这个题目是Google kickstart 2019 roundB的第二题,为防止再翻墙,特将题目粘贴如下:

题目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解析

在这里插入图片描述
在这里插入图片描述

中文解析

题目意思:森林中住着一个rocker,这个rocker有很多的能量石,吃了这个能量石,rocker就能得到这个能量石的能量(E)。但是由于能量石比较硬,吃每个能量石需要一些时间(S)。能量石的能量会随着时间衰减(衰减速度为L)。给N个能量石,问怎么吃,能使得rocker得到最多的能量。

解析:因为总能量是一定的,但是一旦开始,所有的能量石都按照自己的衰减速度在流失能量,因此,我们吃的时候,应该先将能量衰减速度比较快的吃掉,避免丢失太多的能量。假设现在有两个石头

石头编号SEL总能量
iS_iE_iL_iE_i + (E_j - S_i * L_j)
jS_jE_jL_jE_j+ (E_i - S_j * L_i )

假设每个石头最后的能量都不会变成0,则由上表可知,先取石头i时,我们丧失的能量为S_i * L_j,先取石头j时,我们丧失的能量为S_j * L_i。因此,我们可以先按照损失的能量降序对石头进行排序。

class Stone implements Comparable<Stone>{
    int seconds;
    int energy;
    int reduce;
    public Stone(int _seconds, int _energy, int _reduce) {
        this.seconds = _seconds;
        this.energy = _energy;
        this.reduce = _reduce;
    }

    @Override
    public int compareTo(Stone other) {
        long ans = (long)this.seconds * other.reduce - (long)other.seconds * this.reduce;
        if (ans > 0) {
            return 1;
        }
        if (ans < 0) {
            return -1;
        }
        return 0;
    }
}

排序之后,我们就能保证每次取的时候,都将流量损失最大的石头给吃了。之后,吃哪个石头,就看我们怎么选了,因为一个石头可能它的能量比较大,但是它又比较浪费时间,这时候,就需要我们进行权衡。(其实这个和背包问题比较像,每次往背包里放从西的时候,它的价值可能比较大,但是它可能非常浪费空间。)。其实,在排序之后,它是0,1背包问题,能用动态规划解决。

动态规划公式

dp[i][j] 表示在时刻j, 吃完i个石头时能取得的最大价值

  • 其中i的取值范围为[0, N - 1]
  • j 的取值范围为[0, 100 * 100 + 10],(注意: 100 * 100 的原因是,题目中最多有100个石头,
    吃每个石头最多用100s,因此,在100 * 100时,一定能吃完所有的石头。)

最后代码如下:


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

/**
 * dp[i][j]表示到时刻J时,吃完i,能获得的能量
*/

class Stone implements Comparable<Stone>{
    int seconds;
    int energy;
    int reduce;
    public Stone(int _seconds, int _energy, int _reduce) {
        this.seconds = _seconds;
        this.energy = _energy;
        this.reduce = _reduce;
    }

    @Override
    public int compareTo(Stone other) {
        long ans = (long)this.seconds * other.reduce - (long)other.seconds * this.reduce;
        if (ans > 0) {
            return 1;
        }
        if (ans < 0) {
            return -1;
        }
        return 0;
    }
}

public class Solution {
    private static  List<Stone> list = null;
    private static int MAX_TIME = 100 * 100 + 1010;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
        int caseNum = scanner.nextInt();
        for(int i = 0; i < caseNum; i++) {
            int stoneNum = scanner.nextInt();
            list = new ArrayList<>(stoneNum);
            for(int j = 0; j < stoneNum; j++) {
                int second = scanner.nextInt();
                int energy = scanner.nextInt();
                int reduce = scanner.nextInt();
                Stone stone = new Stone(second, energy, reduce);
                list.add(stone);
            }
            Collections.sort(list);
			// dp[i][j] 表示,在时刻j时,吃完前i个石头获得的总能量
            int[][] dp = new int[stoneNum][MAX_TIME];
            int ans = 0;
            for(int j = list.get(0).seconds; j < MAX_TIME; j++) {
                dp[0][j] = Math.max(list.get(0).energy - (j - list.get(0).seconds) * list.get(0).reduce, 0);
                ans = Math.max(ans, dp[0][j]);
            }
            for(int j = 1; j < stoneNum; j++) {
                Stone currStone = list.get(j);
				// 不取当前石头, 则现在获得的总能量和取前j-1个石头在相同的时间获得的能量相同(可类比0,1背包问题)
                for(int k = 0; k < MAX_TIME; k++) {
                    dp[j][k] = dp[j - 1][k];
                }
				// 如果取当前的石头,则现在获得的总能量等于,当前石头的能量加上
                // 在(k - currStone.seconds)时刻,前(j - 1)个石头获得的能量
                for(int k = currStone.seconds; k < MAX_TIME; k++) {
                    int currEnergy = Math.max(currStone.energy - (k - currStone.seconds) * currStone.reduce, 0);
                    dp[j][k] = Math.max(dp[j - 1][k - currStone.seconds] + currEnergy, dp[j][k]);
                    ans = Math.max(ans, dp[j][k]);
                }
            }
            System.out.println("Case #" + (i + 1) + ": " + ans);
        }
    }
}

上面的代码空间复杂度为O(N)*O(10000),时间复杂度也是O(N)*O(10000),它的空间复杂度可以优化为O(10000),即我们用一维的数组代替二维的数组。仔细观察上边的代码,可以发现,每次计算dp[i][j]时,只用到了它上一行的两个值,dp[i -1][j]dp[i-1][j - x],即该列前边的一个数字,因此,我们可以在遍历的时候倒着遍历,这样就不会覆盖之上一行我们需要的值了。照着上边的的代码,我们更改为一维的如下:

一维代码:

package b.EnergyStones;


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

class Stone implements Comparable<Stone>{
    int seconds;
    int energy;
    int reduce;
    public Stone(int _seconds, int _energy, int _reduce) {
        this.seconds = _seconds;
        this.energy = _energy;
        this.reduce = _reduce;
    }

    @Override
    public int compareTo(Stone other) {
        long ans = (long)this.seconds * other.reduce - (long)other.seconds * this.reduce;
        if (ans > 0) {
            return 1;
        }
        if (ans < 0) {
            return -1;
        }
        return 0;
    }
}

public class Solution {
    private static  List<Stone> list = null;
    private static int MAX_TIME = 100 * 100 + 1010;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
        int caseNum = scanner.nextInt();
        for(int i = 0; i < caseNum; i++) {
            int stoneNum = scanner.nextInt();
            list = new ArrayList<>(stoneNum);
            for(int j = 0; j < stoneNum; j++) {
                int second = scanner.nextInt();
                int energy = scanner.nextInt();
                int reduce = scanner.nextInt();
                Stone stone = new Stone(second, energy, reduce);
                list.add(stone);
            }
            Collections.sort(list);
            int[] dp = new int[MAX_TIME];
            int ans = 0;
            for(int j = 1; j <= stoneNum; j++) {
                Stone stone = list.get(j - 1);
                for(int k = MAX_TIME - 1; k >= stone.seconds; k--) {
                    int currE = Math.max(stone.energy - (k - stone.seconds) * stone.reduce, 0);
                    dp[k] = Math.max(dp[k - stone.seconds] + currE, dp[k]);
                    ans = Math.max(ans, dp[k]);
                }
            }
            System.out.println("Case #" + (i + 1) + ": " + ans);
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值