P3092 [USACO13NOV] No Change G 题解
思路
状压 DP。
设 \(dp_i\) 为使用的硬币的状态为 \(i\) 时能够买到的物品。考虑 \(dp_i\) 如何转移到 \(dp_j\)(其中 \(j\) 是在 \(i\) 的基础上再用一枚硬币的状态)。我们需要找到一个点使得可以买到的物品最大,且不会超过当前硬币的大小,即 \(dp_j = \max(dp_j, t)\),其中 \(t\) 是满足 \(\sum \limits_{k = dp_i + 1}^t c_k \le mx\) 的 \(t\) 的最大值。\(mx\) 为这次操作用的硬币面值。
这样时间复杂度 \(\mathcal{O}(k2^kn)\),还需要继续优化。
我们发现 \(c_k > 0\),所以 \(\sum \limits_{k = dp_i + 1}^t c_k\) 单调不降,那就可以预处理 \(sum_i = \sum \limits_{j = 1}^i c_j\) 即 \(c\) 的前缀和,最后二分出最大的 \(t\) 即可。
时间复杂度 \(\mathcal{O}(k2^k \log n)\)。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 17, M = 1e5 + 5;
int k, n;
int val[N], c[M], sum[M], dp[(1 << N) + 1];
signed main()
{
scanf("%lld%lld", &k, &n);
for (int i = 0; i < k; i++)
scanf("%lld", &val[i]);
for (int i = 1; i <= n; i++)
scanf("%lld", &c[i]), sum[i] = sum[i - 1] + c[i];
int ans = 0;
bool f = 0;
for (int i = 0; i < (1 << k); i++)
{
for (int j = 0; j < k; j++)
{
if ((i & (1 << j)) != 0) continue;
int tmp = (dp[i] >= 0 ? sum[dp[i]] : 0);
int ne = i | (1 << j), pos = upper_bound(sum + 1, sum + n + 1, val[j] + tmp) - sum - 1;
dp[ne] = max(dp[ne], pos);
if (dp[ne] == n)
{
f = 1;
int tot = 0;
for (int t = 0; t < k; t++)
if (!((ne >> t) & 1)) tot += val[t];
ans = max(ans, tot);
}
}
}
if (f) printf("%lld\n", ans);
else printf("-1\n");
return 0;
}

浙公网安备 33010602011771号