USACO Training Section 3.3 Shopping Offers

[url="http://ace.delos.com/usacoprob2?a=vKbrJN28FC9&S=shopping"]英文原题[/url]
[url="http://www.wzoi.org/usaco/14%5C307.asp"]中文题译[/url]

[size=small]这是个与日常生活相关的题。商场促销,把许多商品捆绑销售并优惠价格,一个精明的先生拿着老婆给的购物清单来买东西,他准备给自己存点私房钱,所以,他不打算为省钱买不需要的东西(如果是女士来,就会这样),为此他扫描了一遍所有的优惠活动,决定按最优方式来购买以节省最多的钱。

数据限制条件:商品种类最多999项,优惠活动最多999项,每项最多捆绑5件商品,优惠价总是比原价总和便宜。先生要买的物品最多5种,每种最多5件。

分析:虽然商品种类最多999项,但既然先生不准备买多余的东西,可认为只有最多5项,凡是优惠条目中包含不需要的商品的一律忽略。这是预处理过程,不妨设要买的商品就是1-5编号。每样最多5件,状态为6^5=7776,每个状态<a_i>(0<=i<5,0<=a_i<=5)有:
best<a_i>=min{best<a_i-b_j_i>+price_j|j为优惠方案,b_j_i为其中物品i的数量,price_j为其价格},
在优惠方案中加入原始报价(即只有一件物品数量为1)也作为一种方案,即不需特殊处理了。我倾向于用一维数组表示状态而不是5维,更具有一般性吧。

实现:
1. 在字符串缓冲区中读取内容时用sscanf不方便,改用isstream ;
2. DP过程中可以加点优化,对每个数,只要某个数值大于需要的相应位,就可跳过,不需完全搜索。
一次通过,很顺利。[/size]

/*
ID: blackco3
TASK: shopping
LANG: C++
*/
#include <iostream>
#include <sstream>
#include <string>
#include <memory.h>
using namespace std;
const int _max_special_(1000), _max_type_(1000), _max_line_len_(256);
const int _max_type_need_(5), _max_num_need_(5), _max_comb_(7776)/*as (5+1)^5 */;

int n_type(0), n_tot_special(0), need[_max_type_], max_need(0);
int nums[_max_type_], best[_max_comb_] ;
struct t_special {
int n_type, types[_max_type_need_], nums[_max_num_need_], price ;
} specials[_max_special_+_max_type_need_], *ps_end=specials ;

int main() {
freopen("shopping.in", "r", stdin);
freopen("shopping.out", "w", stdout);

char lines[_max_special_][_max_line_len_+1] ;
fgets( lines[0], _max_line_len_, stdin) ;
sscanf( lines[0], "%d", &n_tot_special ) ;
for( int i=0; i<n_tot_special; i++ )
fgets( lines[i], _max_line_len_, stdin );
memset( need, 0xff, sizeof(need) );

cin >> n_type ;
for( int i=0; i<n_type; i++ ){
int type ;
cin >> type >> nums[i] >> ps_end->price ;
need[type] = i , ps_end->n_type=1, ps_end->nums[0] =1 , ps_end->types[0] = i, ps_end++ ;
max_need = nums[i]>max_need ? nums[i] : max_need ;
}

istringstream iss ;
for( int i=0; i<n_tot_special; i++ ){
iss.str(lines[i]), iss >> ps_end->n_type ;
if( ps_end->n_type > n_type )
continue ;
int ignore=false ;
for( int j=0; j<ps_end->n_type; j++ ){
iss >> ps_end->types[j] >> ps_end->nums[j] ;
if( need[ ps_end->types[j] ]==-1 ){
ignore=true ;
break ;
}
ps_end->types[j] = need[ ps_end->types[j] ] ;
}
if( ignore )
continue ;
iss >> ps_end->price ;
ps_end++ ;
}

int n_comb=0, pow[_max_num_need_], cur_num[_max_type_need_] ;
max_need++ ; /* to get 0..max_need*/
for( int i=0 ; i<n_type; i++ ){
n_comb = max_need*n_comb + nums[n_type-1-i];
cur_num[i] = 0 , pow[i] = i ? pow[i-1]*max_need : 1 ;
}

for( int i_comb=1; i_comb<=n_comb; i_comb++ ){
for( int pos=0; (++cur_num[pos])==max_need; pos++)
cur_num[pos] = 0 ;
int min_val=0x7fffffff ;
for( t_special *pc=specials; pc!=ps_end; pc++ ){
int ignore=false, pre_comb=i_comb ;
for( int it=0; it<pc->n_type; it++ ){
if( pc->nums[it] > cur_num[pc->types[it]] ){
ignore=true ;
break ;
}
pre_comb -= pc->nums[it]*pow[pc->types[it]] ;
}
if( ignore )
continue ;
min_val = min_val <= best[pre_comb] + pc->price ? min_val : best[pre_comb] + pc->price ;
}
best[i_comb] = min_val ;
}

cout << best[n_comb] << endl ;
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值