这里是简介
思路
前言
刚看到这道题$k\leq 16$的范围时我以为是暴力枚举…
于是就高高兴兴敲了一个暴枚+贪心骗分…
最后觉得骗分不实在,于是就改成暴枚+全排列+二分查找了…
正文
其实可以用DP来做,不过会MLE,怎么办呢?
这时我们发现: $k\leq 16$ 于是可以压硬币的状态!
于是就变成了状态压缩DP!
转移方程:
设$dpi$表示我们选硬币集合 i(状压)从一号物品开始最多能买的物品数。
于是有:$dp_i=max(dp_i,dp{subset(i)}+coin)$
这里的coin表示选了当前这块硬币后又能买多少东西!
怎么处理coin呢?——用二分查找啊,在原基础上利用前缀和进行二分查找…(前缀和单调递增啊)
当然,你也可以用尺取预先处理(空间换时间)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using std::upper_bound;
typedef long long ll;
inline ll max (ll a, ll b) {return a > b ? a : b;}
const ll K = 16, N = 1e5 + 10;
ll k, n, ce[K], co[N], sum[N], cnt, ans = -1, dp[70000];
//计算当前所剩钱数
inline ll num (ll i) {
ll val = cnt;
for (register ll j = 0; j < k; ++j)
if (i & (1 << j)) val -= ce[j];
return val;
}
int main () {
scanf ("%lld%lld", &k, &n);
for (register ll i = 0; i < k; ++i) {
scanf ("%lld", ce + i);
cnt += ce[i];
}
for (register ll i = 1; i <= n; ++i) {
scanf ("%lld", co + i);
sum[i] = sum[i - 1] + co[i];
}
for (register ll i = 1; i < (1 << k); ++i)
for (register ll j = 0; j < k; ++j)
if (i & (1 << j)) {
ll tmp = i ^ (1 << j);
tmp = upper_bound(sum + 1, sum + n + 1, sum[dp[tmp]] + ce[j]) - sum - 1;
dp[i] = max(dp[i], tmp);
}
for (register ll i = 1; i < (1 << k); ++i)
if (dp[i] == n)
ans = max (ans, num(i));
printf ("%lld\n", ans);
return 0;
}