January 22, 2025

P6808 Candies 题解

鲜花

校内模拟赛T3,赛时想到了正解的 ,所以就得了 分……

赛后 T 了若干发之后终于过了。


本文提供一种非回退背包的解法。

在下文中,记

Solution 1

假设我们修改 ,设用其他数拼出的方案数为 ,那么当 足够大时,有 。所以问题就转化到了求

考虑枚举 ,并对于每个 进行 DP 计算 。求出最大值与最大值位置(即 )后,需要求

表示是否能凑出 。很容易发现,当 时, 合法。暴力枚举即可。

时间复杂度 ,完全不能接受。

Solution 2

我们发现,在第一种解法中,对于同一个物品,进行了太多次的背包 DP。我们尝试只进行一次DP,并标记为了凑出当前这个数,有哪些位置必须被选取。若不需要选取第 个数,代表当我们修改 时仍然能凑出这个数。

具体地,设 表示为了凑出 ,是否需要 。转移方程如下。

$

DP 完之后统计 ,剩余步骤与解法一相同。

时间复杂度仍然是 ,但常数好得多,在洛谷上取得了 分的好成绩!(这也是我赛时的做法,个人认为特别符合直觉。)

Solution 3

考虑优化一下求 的过程。前文已推得,当 不合法。设全集(可重集)为 ,此时有可重集 满足 。再正反做一次背包即可。总时间复杂度 ,还是卡。

Solution 4

由于所有状态都是 ,所以可以用 bitset 优化。最终复杂度 ,趋近于 ,可以通过。

Code

在代码中,dp[i][0] 代表文中的 ,其余的 dp[i][j] 代表文中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int n, a[105], sum, cnt[105], mx, p;
bitset<105> dp[700005];
bitset<1400005> tmp;

int main() {
read(n);
for(int i = 1; i <= n; ++i)
read(a[i]), sum += a[i];
// 初始化 dp 数组
sort(a + 1, a + 1 + n);
dp[0] = 1, dp[1][0] = 0;
for(int i = 1; i <= n; ++i)
dp[1][i] = 1;
for(int i = 2; i <= sum; ++i)
dp[i] = dp[i - 1];
// 计算 dp 数组
for(int i = 1; i <= n; ++i)
for(int j = sum; j >= a[i]; --j)
if(dp[j - a[i]][0]) {
dp[j] &= dp[j - a[i]];
if(!dp[j][0]) dp[j][i] = 1;
dp[j][0] = 1;
}
// 统计 cnt 并计算 p
for(int i = 1; i <= sum; ++i)
if(dp[i][0])
for(int j = 1; j <= n; ++j)
if(!dp[i][j])
++cnt[j];
mx = p = 0;
for(int i = 1; i <= n; ++i)
if(cnt[i] > mx) mx = cnt[i], p = i;
// 计算 q
tmp[7000 * n] = 1;
for(int i = 1; i <= n; ++i)
if(i != p)
tmp = tmp | (tmp << a[i]) | (tmp >> a[i]);
for(int i = 1; i <= sum - a[p] + 1; ++i)
if(!tmp[7000 * n + i])
return printf("%d %d\n", a[p], i), 0;
}

这代码截止发题解当天竟然还跑出了洛谷最优解。(不过做这道题的人少。)

关于本文

由 Getaway_Car 撰写, 采用 CC BY-NC 4.0 许可协议.

#题解