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;
}
posted @ 2026-01-30 16:52  lucasincyber  阅读(2)  评论(0)    收藏  举报