刷题记录:牛客NC24416[USACO 2013 Nov G]No Change

传送门:牛客

题目描述:

约翰到商场购物,他的钱包里有K(1 <= K <= 16)个硬币,面值的范围是1..100,000,000。
约翰想按顺序买 N个物品(1 <= N <= 100,000),第i个物品需要花费c(i)块钱,(1 <= c(i) <= 10,000)。
在依次进行的购买N个物品的过程中,约翰可以随时停下来付款,每次付款只用一个硬币,支付购买的内容
是从上一次支付后开始到现在的这些所有物品(前提是该硬币足以支付这些物品的费用)。不幸的是,商场
的收银机坏了,如果约翰支付的硬币面值大于所需的费用,他不会得到任何找零。
请计算出在购买完N个物品后,约翰最多剩下多少钱。如果无法完成购买,输出-1
输入:
3 6 
12 
15 
10 
6 
3 
3 
2 
3 
7 
输出:
12 

一道典型的状压dp题.

在刷完十几道状压dp题之后,对于此题我居然随便一写就写出来了,大喊一声"真爽",此题在洛谷上还是蓝题

废话不多说,下面是对此题的分析:

  1. 首先我们一看 k k k的范围只有16,大概率是用到状压dp了,在看一下题目,感觉是类似于贪心,但是贪心分析简单分析一下又是错的.那么对于这种大概率就是dp了
  2. 我们往状压dp的那个方向去想,我们用 d p [ S ] dp[S] dp[S]来记录我们用的硬币的状态为 S S S时(我们将硬币用了记为1,没用记为0),我们最大能买的物品的价值.注意此时我们记录的是物品的价值,这样更方便一点,然后因为我们的物品是顺序购买的,所以我们只要记录一下前缀和的话,配上二分就可以很容易的得到我们状态为S时买的物品的数量了
  3. 那么我们的转移也就呼之欲出了,当我们目前状态为S时我们可以枚举剩下的硬币,然后枚举每一个硬币,然后对于每一个新加的硬币,我们用二分找出现在我们能买的最多物品的位置即可
    d p [ S ∣ ( 1 < < i ) ] = m a x ( d p [ S ∣ ( 1 < < i ) ] , s u m [ p − 1 ] ) ; dp[S|(1<<i)]=max(dp[S|(1<<i)],sum[p-1]); dp[S(1<<i)]=max(dp[S(1<<i)],sum[p1]);
  4. 对于我们最终的答案的话,判断一下每一种状态是不是能买到所有的物品了,如果能的话就记录一下,挑出最小值即可

下面是具体的代码部分:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
#include <deque>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
int k,n;int dp[1<<17];int coin[17];int v[100020];int sum[100020];
int main() {
	k=read();n=read();int ans=-1;
	for(int i=0;i<k;i++) {
		coin[i]=read();
	}
	for(int i=1;i<=n;i++) {
		v[i]=read();sum[i]=sum[i-1]+v[i];
	}
	for(int S=0;S<1<<k;S++) {
		for(int i=0;i<k;i++) {
			if((S>>i)&1) continue;
			int p=upper_bound(sum+1,sum+n+1,dp[S]+coin[i])-sum;
			dp[S|(1<<i)]=max(dp[S|(1<<i)],sum[p-1]);
		}
		if(dp[S]>=sum[n]) {
			int Sum=0;
			for(int j=0;j<k;j++) {
				if((S>>j)&1) continue;
				Sum+=coin[j];
			}
			ans=max(ans,Sum);
		}
	}
	if(ans==-1) cout<<-1<<endl;
	else cout<<ans<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值