zoukankan      html  css  js  c++  java
  • poj_3260 动态规划

    题目大意

        顾客拿着N种硬币(币值为value[i], 数量为c[i])去买价值为T的东西,商店老板也有同样N种币值的硬币,但是数量不限。顾客买东西可能需要用硬币找零来使得花出去的钱为T,求顾客给老板的硬币数为count1,老板找回给顾客的硬币数目为count2,求count1 + count2的最小值。

    题目分析

        先通过求和得到顾客的总钱数,若小于T,则说明无法进行交换,直接返回-1;否则,可以进行交换,那么顾客给老板的总钱数V1肯定>=T, 而老板找回给顾客的总钱数为 V1 - T. 
        用顾客手上的硬币去凑成V1的钱,为多重背包问题,通过转换为01背包来解决。这样可以求得 f1[V1] 表示用顾客手上的硬币凑成价值为V1的钱的硬币的最小数目;用老板手上的硬币去凑成V2的钱,为一个完全背包问题,利用完全背包来解决,得到f2[V2]表示用老板手上的硬币凑成价值为V2的钱的硬币的最小数目。 
        然后对V1进行遍历,找到V1-V2 = T,且f[V1]+f[V2]最小的f[V1]+f[V2]即可。 
        需要注意的是初始化

    实现(c++)

    #define _CRT_SECURE_NO_WARNINGS
    //思路是 将要付的钱t 表示成 s1 - s2的形式,其中s1为顾客要给老板的钱数;s2为老板找回的钱数
    //s1 >= t,否则无效
    //两个dp数组f1, f2,其中 f1[i] 表示 顾客用身上的硬币凑成 i元所消耗的最少硬币数目
    //f2[i] 表示老板要找i元所消耗的最少硬币数目
    //由于顾客硬币数目有限制,为01背包; 老板硬币数目无限制,为完全背包
    
    #include<stdio.h>
    #include<string.h>
    #define MAX_CENTS 20200
    #define COIN_TYPE_NUM 102
    #define INFINITE 1 << 28
    int gCoinValue[MAX_CENTS]; //将多重背包转换为01背包之后的硬币的币值
    int gCoinNum[MAX_CENTS];   //将多重背包转换为01背包之后每个硬币值代表的实际硬币个数
    
    int f1[MAX_CENTS];
    int f2[MAX_CENTS];
    
    //多重背包转01背包,二进制优化
    //将数字n分解为 集合S{1, 2, 4, 。。。2^(k-1), n - 2^k + 1}
    //数字[1,n]内的任何数字都可以用集合S中的多个数字拼成
    //其中k为满足 n - 2^k + 1 > 0的最大的k
    void Expand(int v, int n, int& index){
    	int k = 1;
    	do{
    		gCoinNum[index] = k;
    		gCoinValue[index++] = k*v;
    		k *= 2;
    
    	} while (2*k < n);
    	gCoinNum[index] = (n - k + 1);
    	gCoinValue[index++] = (n - k + 1)*v;
    }
    int min(int a, int b){
    	return a < b ? a : b;
    }
    
    
    int main(){
    		int n, t, index = 0;
    		int coin_value[COIN_TYPE_NUM];
    		scanf("%d %d", &n, &t);
    		for (int i = 0; i < n; i++){
    			scanf("%d", coin_value + i);
    		}
    		int coin_num, sum_value = 0;
    		for (int i = 0; i < n; i++){
    			scanf("%d", &coin_num);
    			sum_value += coin_num*(coin_value[i]);
    			Expand(coin_value[i], coin_num, index);
    		}
    		if (sum_value < t){		//直接判断,总的钱数是否够物品价值,不够则直接返回失败
    			printf("-1
    ");
    			return 0;
    		}
    
    		memset(f1, 0, sizeof(f1));
    		memset(f2, 0, sizeof(f2));
    		int m = t + MAX_CENTS / 2;  //范围,猜测的。。。
    
    		//进行合理的初始化
    		for (int i = 0; i <= m; i++){
    			f1[i] = f2[i] = INFINITE;
    		}
    
    		f1[0] = 0;		//一个硬币都没有,能够构成总价值为0的最少 硬币数目为0
    		f2[0] = 0;		
    
    		f1[gCoinValue[0]] = 1;	//用前1个硬币进行初始化,即用前1种硬币凑成 coin_value[0] 价值的硬币的最少数目
    		f2[coin_value[0]] = 1;
    
    		for (int i = 1; i < index; i++){	//多重背包转换为01背包,利用前一个物品初始化之后,从前2个物品开始循环
    			for (int w = m; w >= gCoinValue[i]; w--){
    				f1[w] = min(f1[w], f1[w - gCoinValue[i]] + gCoinNum[i]);
    			}
    		}
    
    		for (int i = 0; i < n; i++){		//完全背包,从前1个物品开始
    			for (int w = coin_value[i]; w <= m - t; w++){
    				f2[w] = min(f2[w], f2[w - coin_value[i]] + 1);
    			}
    		}
    		int min_coin_num = INFINITE;
    		for (int i = t; i <= m; i++){
    			min_coin_num = min(min_coin_num, f1[i] + f2[i - t]);
    		}
    		if (min_coin_num == INFINITE)  //判断是否能够 拼成 t
    			printf("-1
    ");
    		else
    			printf("%d
    ", min_coin_num);
    	return 0;
    }
    
  • 相关阅读:
    es之零停机重新索引数据
    es之索引的别名操作
    es索引基本操作(2)之 索引映射(mappings)管理和索引库配置管理(settings)
    进程管理(八)-进程控制
    进程管理(七)-进程状态与转换
    进程管理(六)-进程的描述
    numpy数组转置与轴变换
    numpy数组的索引和切片
    numpy数组的运算
    numpy库中数组的数据类型
  • 原文地址:https://www.cnblogs.com/gtarcoder/p/4840108.html
Copyright © 2011-2022 走看看