ACM解题总结——HihoCoder1364

题目来源:

    HihoCoder1364 

题目要求:

    小Hi在游乐园中获得了M张奖券,这些奖券可以用来兑换奖品。

    可供兑换的奖品一共有N件。第i件奖品需要Wi张奖券才能兑换到,其价值是Pi。  

    小Hi使用不超过M张奖券所能兑换到的最大奖品总价值是多少?

输入输出格式:

    输入:

第一行两个整数NM。  

接下来N行,每行两个整数WiPi。  

对于 50%的数据: 1≤N,M≤1000  

对于 100%的数据: 1≤N,M≤105,1≤Pi,Wi≤10。  

输出:

一行一个整数,表示答案。


解答:

·概述:

    本题猛的一看就是一个典型的01背包问题,但是笔者亲测发现,由于数据量比较大,直接使用01背包的动态规划方式会超时,因此需要在原有背包问题的基础上进行一些优化。
    考虑到WiPi的大小范围均为[1,10],因此,根据WiPi的不同组合,不同种类的商品数目最多为100种。对于商品个数N的最大值为
105,其中一定包含重复的商品,我们通过统计每一种商品出现的次数来对本问题进行优化。对于某一个价格为Wi,价值为Pi的商品,如果其出现次数为C次,则可以利用如下的二进制优化法来进行优化。

·整数的二进制拆分法:

    在说明二进制优化法前,首先介绍一个定理,如下:

    对于任意的整数X,均可以将其表示为多个2的连续幂值之和再加一个余项的形式,即:

        X = 2^0 + 2^1 + ... + 2^k + R

例如,当X=100时,可以表示为:
        100 = X = 1 + 2 + 4 + 8 + 16 + 32 + 37
其中,余项R的值为37
    接下来,为了便于描述,这里记2k次幂为M[k],因此,对于任意的整数X,都可表示为如下的形式:
        X = M[0] + M[1] + ... + M[k] + R,
显然,这里的k值满足这样的条件:
M[0] + M[1] + ... + M[k] ≤ X,且M[0] + M[1] + ... + M[k] + M[k+1] > X,通过简单的计算,可以得到k的值为floor(log(x+1)) - 1,对于上面X=100的例子,k的值为5
此时,有如下的结论: 

    对于任意的不大于X的非负整数Y,均可从M[0], M[1], ... M[k], R中的k+1个数中选择若干数字,其和恰好为Y

对于这个结论,可以用分两种情况加以证明:
    如果Y≤M[k+1],那么Y一定可以通过
M[0], M[1], ... M[k]中的若干数字的和来表示,只要将Y转换为二进制格式,将各个值为1的对应位的Mi值相加即可。例如,对于上面的例子,当Y的值为35时,转换为二进制为:100011,其中第0位,第1位和第5位为1,所以Y = M[0] + M[2] + M[5] = 1 + 2 + 32 = 35;
    对于Y>M[k+1]的情况,则可以知道Y-R一定是一个不大于M[k+1]的数字,可以用反证法证明:如果
Y-R>M[k+1],那么Y-R+R> M[k+1]+R > M[0] + M[1] + ... + M[k] + R = X,这和Y≤X的条件是矛盾的,因此Y-R≤M[k+1],根据前面的结论,Y-R一定可以用M[0], M[1], ... M[k]中的若干数字的和来表示,那么,Y就可以表示为R以及M[0], M[1], ... M[k]中的若干数字的和。
    综上所述,对于任意的Y≤X,均可以用R
M[0], M[1], ... M[k]中的若干数字的和来表示。

·01背包问题的二进制优化法:

    有了以上结论后,在回到本题,对于某一个价格为Wi,价值为Pi的商品,如果其出现次数为C次,根据上面的定理,出现次数C可以表示为如下的形式:
    C = M[0] + M[1] + ... + M[k] + R
此时,我们将原题中C个商品替换为如下的k+1个商品:
    1个价格为M[0]*Wi,价值为M[0]*Pi的商品;
    
1个价格为M[1]*Wi,价值为M[1]*Pi的商品;
    
1个价格为M[2]*Wi,价值为M[2]*Pi的商品;
    ...
    
1个价格为M[k]*Wi,价值为M[k]*Pi的商品;
    1个价格为R*Wi,价值为R*Pi的商品;
根据前面的结论,对于任意的小于C的非负整数,均可以用RM[0], M[1], ... M[k]中的若干数字的和来表示,所以,不论问题的最优方案中选择了多少个当前类型的商品,将C个原来的商品替换为现在的k+1个商品后,均可以找到等价的方案,因此,替换过程不会影响本题的结果。
    由k的值为
floor(log(x+1)) - 1,因此,当C的值很大时,把C个商品替换为k+1个商品时,可以大幅度地缩减问题规模,从而提高程序效率。

    
将输入中的每一种商品都做一次这样的优化后,商品数目将大大较少,再使用01背包问题的解法进行求解,就可以得到本题的正确解法。

程序代码:

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * This is the ACM problem solving program for hihoCoder 1364.
 * 
 * @version 2018-03-24
 * @author Zhang Yufei.
 */
public class Main {
    /**
     * Record the commodity information.
     */
    private static class Commodity {
        int price;
        int value;
    }
    
    /** For input. */
    private static Scanner scan;

    /** For output. */
    private static PrintWriter writer;

    /** Input data */
    private static int M, N;
    
    /** Commodity list */
    private static List<Commodity> commodities;
    
    /** 
     *  Used for DP, in i-th iterator, dp[j] means the maximum 
     *  prize given i commodities and j token.
     */
    private static int[] dp;
    
    /**
     * Used for optimization, matrix[i][j] records the amount
     * of commodities whose price is i and value is j.
     */
    private static int[][] matrix;

    /**
     * The main program.
     * 
     * @param args
     *            The command-line parameters list.
     */
    public static void main(String[] args) {
        initIO();
        
        input();
        
        dp = new int[M + 1];
        
        for(int j = 0; j <= M; j++) {
            dp[j] = 0;
        }
        
        for(int i = 0; i < N; i++) {
            for(int j = M; j >= 0; j--) {
                int r1 = dp[j];
                int r2 = 0;
                if(j - commodities.get(i).price >= 0) {
                    r2 = dp[j - commodities.get(i).price] + commodities.get(i).value;
                }
                
                dp[j] = r1 > r2 ? r1 : r2;
            }
        }
        
        System.out.println(dp[M]);
        
        closeIO();
    }

    /**
     * Deal with input.
     */
    private static void input() {
        N = scan.nextInt();
        M = scan.nextInt();
        
        matrix = new int[11][11];
        for(int i = 0; i < 11; i++) {
            for(int j = 0; j < 11; j++) {
                matrix[i][j] = 0;
            }
        }
        
        for(int i = 0; i < N; i++) {
            int W, P;
            W = scan.nextInt();
            P = scan.nextInt();
            
            matrix[W][P]++;
        }
        
        commodities = new ArrayList<>();
        for(int i = 1; i <= 10; i++) {
            for(int j = 1; j <= 10; j++) {
                int count = matrix[i][j];
                if(count > 0) {
                    int s = 1;
                    while(count >= s) {
                        Commodity c = new Commodity();
                        c.price = s * i;
                        c.value = s * j;
                        
                        commodities.add(c);
                        
                        count -= s;
                        s *= 2;
                    }
                    
                    if(count > 0) {
                        Commodity c = new Commodity();
                        c.price = count * i;
                        c.value = count * j;
                        
                        commodities.add(c);
                    }
                }
            }
        }
        
        N = commodities.size();
    }
    
    /**
     * Initial the input and output.
     */
    private static void initIO() {
        try {
            scan = new Scanner(System.in);
            writer = new PrintWriter(System.out);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Close the input and output.
     */
    private static void closeIO() {
        scan.close();
        writer.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值